diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index ec895d0d87a..25b30662416 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -234,6 +234,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Tests.Stress. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore", "test\TestApp.AspNetCore\TestApp.AspNetCore.csproj", "{5FDAF679-DE5A-4C73-A49B-8ABCF2399229}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "redaction", "docs\logs\redaction\redaction.csproj", "{A2DF46DE-50D7-4887-8C9D-4BD79CA19FAA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -476,6 +478,10 @@ Global {5FDAF679-DE5A-4C73-A49B-8ABCF2399229}.Debug|Any CPU.Build.0 = Debug|Any CPU {5FDAF679-DE5A-4C73-A49B-8ABCF2399229}.Release|Any CPU.ActiveCfg = Release|Any CPU {5FDAF679-DE5A-4C73-A49B-8ABCF2399229}.Release|Any CPU.Build.0 = Release|Any CPU + {A2DF46DE-50D7-4887-8C9D-4BD79CA19FAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2DF46DE-50D7-4887-8C9D-4BD79CA19FAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2DF46DE-50D7-4887-8C9D-4BD79CA19FAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2DF46DE-50D7-4887-8C9D-4BD79CA19FAA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -512,6 +518,7 @@ Global {6C7A1595-36D6-4229-BBB5-5A6B5791791D} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} {9A07D215-90AC-4BAF-BCDB-73D74FD3A5C5} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} {5FDAF679-DE5A-4C73-A49B-8ABCF2399229} = {77C7929A-2EED-4AA6-8705-B5C443C8AA0F} + {A2DF46DE-50D7-4887-8C9D-4BD79CA19FAA} = {3862190B-E2C5-418E-AFDC-DB281FB5C705} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521} diff --git a/docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs b/docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs deleted file mode 100644 index 4e5f3107460..00000000000 --- a/docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -using System.Collections; -using System.Collections.Generic; - -internal class MyClassWithRedactionEnumerator : IReadOnlyList> -{ - private readonly IReadOnlyList> state; - - public MyClassWithRedactionEnumerator(IReadOnlyList> state) - { - this.state = state; - } - - public int Count => this.state.Count; - - public KeyValuePair this[int index] => this.state[index]; - - public IEnumerator> GetEnumerator() - { - foreach (var entry in this.state) - { - var entryVal = entry.Value; - if (entryVal != null && entryVal.ToString() != null && entryVal.ToString().Contains("")) - { - yield return new KeyValuePair(entry.Key, "newRedactedValueHere"); - } - else - { - yield return entry; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } -} diff --git a/docs/logs/extending-the-sdk/Program.cs b/docs/logs/extending-the-sdk/Program.cs index 2ea9dad4953..2a2703f6efe 100644 --- a/docs/logs/extending-the-sdk/Program.cs +++ b/docs/logs/extending-the-sdk/Program.cs @@ -28,8 +28,7 @@ public static void Main() builder.AddOpenTelemetry(options => { options.IncludeScopes = true; - options.AddProcessor(new MyRedactionProcessor()) - .AddProcessor(new MyProcessor("ProcessorA")) + options.AddProcessor(new MyProcessor("ProcessorA")) .AddProcessor(new MyProcessor("ProcessorB")) .AddProcessor(new SimpleLogRecordExportProcessor(new MyExporter("ExporterX"))) .AddMyExporter(); diff --git a/docs/logs/redaction/MyClassWithRedactionEnumerator.cs b/docs/logs/redaction/MyClassWithRedactionEnumerator.cs new file mode 100644 index 00000000000..7cac155957b --- /dev/null +++ b/docs/logs/redaction/MyClassWithRedactionEnumerator.cs @@ -0,0 +1,61 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Collections; +using System.Collections.Generic; + +namespace Redaction +{ + internal class MyClassWithRedactionEnumerator : IReadOnlyList> + { + private readonly IReadOnlyList> state; + + public MyClassWithRedactionEnumerator(IReadOnlyList> state) + { + this.state = state; + } + + public int Count => this.state.Count; + + public KeyValuePair this[int index] + { + get + { + var item = this.state[index]; + var entryVal = item.Value; + if (entryVal != null && entryVal.ToString() != null && entryVal.ToString().Contains("")) + { + return new KeyValuePair(item.Key, "newRedactedValueHere"); + } + + return item; + } + } + + public IEnumerator> GetEnumerator() + { + for (var i = 0; i < this.Count; i++) + { + yield return this[i]; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } +} diff --git a/docs/logs/extending-the-sdk/MyRedactionProcessor.cs b/docs/logs/redaction/MyRedactionProcessor.cs similarity index 72% rename from docs/logs/extending-the-sdk/MyRedactionProcessor.cs rename to docs/logs/redaction/MyRedactionProcessor.cs index 2ad8371ae69..70c704dbad0 100644 --- a/docs/logs/extending-the-sdk/MyRedactionProcessor.cs +++ b/docs/logs/redaction/MyRedactionProcessor.cs @@ -18,11 +18,19 @@ using OpenTelemetry; using OpenTelemetry.Logs; +namespace Redaction; + internal class MyRedactionProcessor : BaseProcessor { public override void OnEnd(LogRecord logRecord) { - if (logRecord.State is IReadOnlyList> listOfKvp) + if (logRecord.State == null) + { + // When State is null, OTel SDK guarantees StateValues is populated + // TODO: Debug.Assert? + logRecord.StateValues = new MyClassWithRedactionEnumerator(logRecord.StateValues); + } + else if (logRecord.State is IReadOnlyList> listOfKvp) { logRecord.State = new MyClassWithRedactionEnumerator(listOfKvp); } diff --git a/docs/logs/redaction/Program.cs b/docs/logs/redaction/Program.cs new file mode 100644 index 00000000000..f381856234e --- /dev/null +++ b/docs/logs/redaction/Program.cs @@ -0,0 +1,38 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; + +namespace Redaction; + +public class Program +{ + public static void Main() + { + using var loggerFactory = LoggerFactory.Create(builder => + builder.AddOpenTelemetry(options => + { + options.AddProcessor(new MyRedactionProcessor()); + options.AddConsoleExporter(); + })); + + var logger = loggerFactory.CreateLogger(); + + // message will be redacted by MyRedactionProcessor + logger.LogInformation("OpenTelemetry {sensitiveString}.", ""); + } +} diff --git a/docs/logs/redaction/README.md b/docs/logs/redaction/README.md new file mode 100644 index 00000000000..07fe87b94bc --- /dev/null +++ b/docs/logs/redaction/README.md @@ -0,0 +1,6 @@ +# Redaction + +This example shows how to redact sensitive information from Logs. +In this example, we attach a custom `Processor` called `MyRedactionProcessor` +which is responsible for replacing any instance of the word "<secret>" +with the value "newRedactedValueHere". diff --git a/docs/logs/redaction/redaction.csproj b/docs/logs/redaction/redaction.csproj new file mode 100644 index 00000000000..a0d4b1e148b --- /dev/null +++ b/docs/logs/redaction/redaction.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs index f089b0e95a2..811838e20c0 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleLogRecordExporter.cs @@ -84,7 +84,28 @@ public override ExportResult Export(in Batch batch) if (logRecord.State != null) { - this.WriteLine($"{"LogRecord.State:",-RightPaddingLength}{logRecord.State}"); + if (logRecord.State is IReadOnlyList> listKvp) + { + this.WriteLine("LogRecord.State (Key:Value):"); + for (int i = 0; i < listKvp.Count; i++) + { + // Special casing {OriginalFormat} + // See https://github.com/open-telemetry/opentelemetry-dotnet/pull/3182 + // for explanation. + var valueToTransform = listKvp[i].Key.Equals("{OriginalFormat}") + ? new KeyValuePair("OriginalFormat (a.k.a Body)", listKvp[i].Value) + : listKvp[i]; + + if (ConsoleTagTransformer.Instance.TryTransformTag(listKvp[i], out var result)) + { + this.WriteLine($"{string.Empty,-4}{result}"); + } + } + } + else + { + this.WriteLine($"{"LogRecord.State:",-RightPaddingLength}{logRecord.State}"); + } } else if (logRecord.StateValues != null) {