Skip to content

Commit c59d622

Browse files
ganeshnjandrewlockbouwkast
authored andcommitted
feat(libdatadog-logger): integrate libdatadog logging (#7075)
## Summary of changes Fixes https://datadoghq.atlassian.net/browse/APMSP-1835 This PR adds enabling the file based logging for libdatadog, ie we start to write the logs to the configured file as it happens. ## Reason for change This is missing feature in the native exporter and must have for troubleshooting. ## Implementation details This PR DataDog/libdatadog#1018 does the bulk of the logging setup. In this, we are only calling the exposed APIs. ## Test coverage Since all the coverage is done in libdatadog there nothing to test in CI, however I verified manually that log file is created and written. ## Other details <!-- Fixes #{issue} --> We will need DataDog/libdatadog#1088 for fully support the file based logging practices. Also, we will need a release of libdatadog to pass the CI. <!-- ⚠️ Note: where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. --> --------- Co-authored-by: Andrew Lock <[email protected]> Co-authored-by: Steven Bouwkamp <[email protected]>
1 parent 8a7cfab commit c59d622

File tree

20 files changed

+456
-103
lines changed

20 files changed

+456
-103
lines changed

tracer/src/Datadog.Trace/Configuration/GlobalSettings.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ internal static void SetDebugEnabledInternal(bool enabled)
107107
DatadogLogging.UseDefaultLevel();
108108
}
109109

110+
LibDatadog.Logger.Instance.SetLogLevel(debugEnabled: enabled);
111+
110112
TelemetryFactory.Config.Record(ConfigurationKeys.DebugEnabled, enabled, ConfigurationOrigins.Code);
111113
}
112114

@@ -119,6 +121,7 @@ public static void Reload()
119121
{
120122
TelemetryFactory.Metrics.Record(PublicApiUsage.GlobalSettings_Reload);
121123
DatadogLogging.Reset();
124+
LibDatadog.Logger.Instance.SetLogLevel(debugEnabled: false);
122125
GlobalConfigurationSource.Reload();
123126
Instance = CreateFromDefaultSources();
124127
}

tracer/src/Datadog.Trace/LibDatadog/Error.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// </copyright>
55

66
#nullable enable
7+
78
using System;
89
using System.Runtime.InteropServices;
910
using System.Text;
@@ -12,23 +13,17 @@
1213
namespace Datadog.Trace.LibDatadog;
1314

1415
/// <summary>
16+
/// Represents a generic error with message returned by the libdatadog library.
1517
/// Do not change the values of this enum unless you really need to update the interop mapping.
1618
/// Libdatadog interop mapping of https://github.com/DataDog/libdatadog/blob/60583218a8de6768f67d04fcd5bc6443f67f516b/ddcommon-ffi/src/error.rs#L14
1719
/// </summary>
1820
[StructLayout(LayoutKind.Sequential)]
19-
internal struct Error
21+
internal readonly struct Error
2022
{
21-
public VecU8 ErrorMessage;
23+
internal readonly VecU8 Message;
2224

23-
internal static string Read(ref Error resultErr)
25+
public LibDatadogException ToException()
2426
{
25-
var message = resultErr.ErrorMessage;
26-
if (message.Length == 0)
27-
{
28-
return string.Empty;
29-
}
30-
31-
var errorMessage = NativeStringHelpers.ReadUtf8NativeString(resultErr.ErrorMessage);
32-
return errorMessage;
27+
return new LibDatadogException(Message.ToUtf8String());
3328
}
3429
}

tracer/src/Datadog.Trace/LibDatadog/ErrorHandle.cs

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,47 +11,35 @@
1111

1212
namespace Datadog.Trace.LibDatadog;
1313

14-
internal class ErrorHandle : SafeHandle
14+
internal class ErrorHandle() : SafeHandle(IntPtr.Zero, true)
1515
{
1616
private static readonly IDatadogLogger Logger = DatadogLogging.GetLoggerFor<ErrorHandle>();
1717

18-
public ErrorHandle()
19-
: base(IntPtr.Zero, true)
20-
{
21-
}
22-
23-
public ErrorHandle(IntPtr handle)
24-
: base(handle, true)
25-
{
26-
SetHandle(handle);
27-
}
28-
2918
public override bool IsInvalid => handle == IntPtr.Zero;
3019

31-
protected override bool ReleaseHandle()
20+
public void ThrowIfError()
3221
{
33-
try
34-
{
35-
NativeInterop.Exporter.FreeError(handle);
36-
}
37-
catch (Exception ex)
22+
if (!IsInvalid)
3823
{
39-
Logger.Error(ex, "An error occurred while releasing the handle for ErrorHandle.");
24+
var error = Marshal.PtrToStructure<Error>(handle);
25+
throw error.ToException();
4026
}
41-
42-
return true;
43-
}
44-
45-
public TraceExporterException ToException()
46-
{
47-
return new TraceExporterException(Marshal.PtrToStructure<TraceExporterError>(handle));
4827
}
4928

50-
public void ThrowIfError()
29+
protected override bool ReleaseHandle()
5130
{
5231
if (!IsInvalid)
5332
{
54-
throw ToException();
33+
try
34+
{
35+
NativeInterop.Common.Drop(this);
36+
}
37+
catch (Exception ex)
38+
{
39+
Logger.Error(ex, "Failed to drop error handle");
40+
}
5541
}
42+
43+
return true;
5644
}
5745
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// <copyright file="FileConfig.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System.Runtime.InteropServices;
9+
10+
namespace Datadog.Trace.LibDatadog;
11+
12+
/// <summary>
13+
/// Represents a configuration for a file logger.
14+
/// </summary>
15+
[StructLayout(LayoutKind.Sequential)]
16+
internal struct FileConfig
17+
{
18+
/// <summary>
19+
/// The path to the log file.
20+
/// If the intermediate directory does not exist, it will be created while configuring the logger.
21+
/// </summary>
22+
public CharSlice Path;
23+
24+
/// <summary>
25+
/// The maximum total number of files (current + rotated) to keep on disk.
26+
/// When this limit is exceeded, the oldest rotated files are deleted.
27+
/// Set to 0 to disable file cleanup.
28+
/// </summary>
29+
public ulong MaxFiles;
30+
31+
/// <summary>
32+
/// The maximum size in bytes for each log file.
33+
/// Set to 0 to disable size-based rotation.
34+
/// </summary>
35+
public ulong MaxSizeBytes;
36+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// <copyright file="LibDatadogException.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
10+
namespace Datadog.Trace.LibDatadog;
11+
12+
internal class LibDatadogException : Exception
13+
{
14+
public LibDatadogException(string message)
15+
: base(message)
16+
{
17+
}
18+
19+
public LibDatadogException(string message, Exception innerException)
20+
: base(message, innerException)
21+
{
22+
}
23+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// <copyright file="LogEventLevel.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
namespace Datadog.Trace.LibDatadog;
9+
10+
/// <summary>
11+
/// Represents the level of a log event.
12+
/// <remarks>
13+
/// The levels are mapped to the levels defined in the libdatadog library.
14+
/// In case of a missing direct mapping, the closest level should be used.
15+
/// </remarks>
16+
/// </summary>
17+
internal enum LogEventLevel
18+
{
19+
Trace = 0,
20+
Debug = 1,
21+
Info = 2,
22+
Warn = 3,
23+
Error = 4,
24+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// <copyright file="LogEventLevelExtension.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
10+
namespace Datadog.Trace.LibDatadog;
11+
12+
internal static class LogEventLevelExtension
13+
{
14+
public static LogEventLevel ToLogEventLevel(this Vendors.Serilog.Events.LogEventLevel level)
15+
=> level switch
16+
{
17+
Vendors.Serilog.Events.LogEventLevel.Verbose => LogEventLevel.Trace,
18+
Vendors.Serilog.Events.LogEventLevel.Debug => LogEventLevel.Debug,
19+
Vendors.Serilog.Events.LogEventLevel.Information => LogEventLevel.Info,
20+
Vendors.Serilog.Events.LogEventLevel.Warning => LogEventLevel.Warn,
21+
// We don't have a "Fatal" level in libdatadog, so we map everything else to Error
22+
// We also don't want to risk throwing
23+
_ => LogEventLevel.Error,
24+
};
25+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// <copyright file="Logger.cs" company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
6+
#nullable enable
7+
8+
using System;
9+
using System.Globalization;
10+
using System.IO;
11+
using Datadog.Trace.Logging;
12+
using Datadog.Trace.Logging.Internal.Configuration;
13+
using Datadog.Trace.Util;
14+
15+
namespace Datadog.Trace.LibDatadog;
16+
17+
internal sealed class Logger
18+
{
19+
private static readonly IDatadogLogger Log = DatadogLogging.GetLoggerFor<Logger>();
20+
21+
private bool _loggingEnabled = false;
22+
23+
public static Logger Instance { get; } = new();
24+
25+
public void Enable(FileLoggingConfiguration fileConfig, DomainMetadata domainMetadata)
26+
{
27+
// Rebuild the logger even if it has already been enabled, in case any configuration has changed
28+
29+
var filePath = Path.Combine(
30+
fileConfig.LogDirectory,
31+
$"dotnet-tracer-libdatadog-{domainMetadata.ProcessName}-{domainMetadata.ProcessId.ToString(CultureInfo.InvariantCulture)}.log");
32+
33+
using var path = new CharSlice(filePath);
34+
var cfg = new FileConfig
35+
{
36+
Path = path,
37+
MaxFiles = 31, // Matches existing Serilog configuration
38+
MaxSizeBytes = (ulong)fileConfig.MaxLogFileSizeBytes,
39+
};
40+
41+
try
42+
{
43+
Log.Debug("Enabling libdatadog logger");
44+
using var error = NativeInterop.Logger.ConfigureFile(cfg);
45+
error.ThrowIfError();
46+
_loggingEnabled = true;
47+
}
48+
catch (Exception ex)
49+
{
50+
_loggingEnabled = false;
51+
Log.Error(ex, "Failed to configure libdatadog logger");
52+
}
53+
}
54+
55+
public void Disable()
56+
{
57+
if (!_loggingEnabled)
58+
{
59+
Log.Debug("Libdatadog logging is not enabled, skipping disable operation.");
60+
return;
61+
}
62+
63+
try
64+
{
65+
Log.Debug("Disabling libdatadog logger");
66+
using var error = NativeInterop.Logger.DisableFile();
67+
error.ThrowIfError();
68+
_loggingEnabled = false;
69+
}
70+
catch (Exception ex)
71+
{
72+
Log.Error(ex, "Failed to disable libdatadog logger");
73+
}
74+
}
75+
76+
public void SetLogLevel(bool debugEnabled)
77+
{
78+
var logLevel = debugEnabled ? Vendors.Serilog.Events.LogEventLevel.Debug : DatadogLogging.DefaultLogLevel;
79+
SetLogLevel(logLevel);
80+
}
81+
82+
private void SetLogLevel(Vendors.Serilog.Events.LogEventLevel logLevel)
83+
{
84+
if (!_loggingEnabled)
85+
{
86+
// This could happen if, for example, datapipeline is enabled, and we trigger a tracer flare to change the log level.
87+
Log.Information("Attempted to set libdatadog log level to {LogLevel} without enabling logging first", logLevel);
88+
return;
89+
}
90+
91+
var level = logLevel.ToLogEventLevel();
92+
try
93+
{
94+
using var error = NativeInterop.Logger.SetLogLevel(level);
95+
error.ThrowIfError();
96+
}
97+
catch (Exception ex)
98+
{
99+
Log.Error(ex, "Failed to set libdatadog log level");
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)