diff --git a/docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs b/docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs
new file mode 100644
index 00000000000..4e5f3107460
--- /dev/null
+++ b/docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs
@@ -0,0 +1,53 @@
+//
+// 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/MyRedactionProcessor.cs b/docs/logs/extending-the-sdk/MyRedactionProcessor.cs
new file mode 100644
index 00000000000..2ad8371ae69
--- /dev/null
+++ b/docs/logs/extending-the-sdk/MyRedactionProcessor.cs
@@ -0,0 +1,30 @@
+//
+// 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.Generic;
+using OpenTelemetry;
+using OpenTelemetry.Logs;
+
+internal class MyRedactionProcessor : BaseProcessor
+{
+ public override void OnEnd(LogRecord logRecord)
+ {
+ if (logRecord.State is IReadOnlyList> listOfKvp)
+ {
+ logRecord.State = new MyClassWithRedactionEnumerator(listOfKvp);
+ }
+ }
+}
diff --git a/docs/logs/extending-the-sdk/Program.cs b/docs/logs/extending-the-sdk/Program.cs
index 02bdf14bc4c..2ea9dad4953 100644
--- a/docs/logs/extending-the-sdk/Program.cs
+++ b/docs/logs/extending-the-sdk/Program.cs
@@ -28,7 +28,8 @@ public static void Main()
builder.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
- options.AddProcessor(new MyProcessor("ProcessorA"))
+ options.AddProcessor(new MyRedactionProcessor())
+ .AddProcessor(new MyProcessor("ProcessorA"))
.AddProcessor(new MyProcessor("ProcessorB"))
.AddProcessor(new SimpleLogRecordExportProcessor(new MyExporter("ExporterX")))
.AddMyExporter();
@@ -64,6 +65,9 @@ public static void Main()
{
logger.LogError("{name} is broken.", "refrigerator");
}
+
+ // message will be redacted by MyRedactionProcessor
+ logger.LogInformation("OpenTelemetry {sensitiveString}.", "");
}
internal struct Food
diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt
index e69de29bb2d..770b89a8996 100644
--- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
+OpenTelemetry.Logs.LogRecord.State.set -> void
+OpenTelemetry.Logs.LogRecord.StateValues.set -> void
\ No newline at end of file
diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
index e69de29bb2d..770b89a8996 100644
--- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
+OpenTelemetry.Logs.LogRecord.State.set -> void
+OpenTelemetry.Logs.LogRecord.StateValues.set -> void
\ No newline at end of file
diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md
index b19ef2e6bfc..2b3bd30138f 100644
--- a/src/OpenTelemetry/CHANGELOG.md
+++ b/src/OpenTelemetry/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+* Exposed public setters for `LogRecord.State`, `LogRecord.StateValues`,
+ and `LogRecord.FormattedMessage`.
+ ([#3217](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3217))
+
## 1.3.0-beta.1
Released 2022-Apr-15
diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs
index a73d11c9cbd..1059ff09829 100644
--- a/src/OpenTelemetry/Logs/LogRecord.cs
+++ b/src/OpenTelemetry/Logs/LogRecord.cs
@@ -81,21 +81,21 @@ internal LogRecord(
public EventId EventId { get; }
- public string FormattedMessage { get; }
+ public string FormattedMessage { get; set; }
///
- /// Gets the raw state attached to the log. Set to when is enabled.
///
- public object State { get; }
+ public object State { get; set; }
///
- /// Gets the parsed state values attached to the log. Set when is enabled
/// otherwise .
///
- public IReadOnlyList> StateValues { get; }
+ public IReadOnlyList> StateValues { get; set; }
public Exception Exception { get; }
diff --git a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs
index 530edc97dc8..37dd5615106 100644
--- a/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs
+++ b/test/OpenTelemetry.Tests/Logs/LogRecordTest.cs
@@ -33,6 +33,13 @@ namespace OpenTelemetry.Logs.Tests
{
public sealed class LogRecordTest
{
+ private enum Field
+ {
+ FormattedMessage,
+ State,
+ StateValues,
+ }
+
[Fact]
public void CheckCategoryNameForLog()
{
@@ -243,6 +250,122 @@ public void CheckStateForExceptionLogged()
Assert.Equal(message, state.ToString());
}
+ [Fact]
+ public void CheckStateCanBeSet()
+ {
+ using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: null);
+ var logger = loggerFactory.CreateLogger();
+
+ var message = $"This does not matter.";
+ logger.LogInformation(message);
+
+ var logRecord = exportedItems[0];
+ logRecord.State = "newState";
+
+ var expectedState = "newState";
+ Assert.Equal(expectedState, logRecord.State);
+ }
+
+ [Fact]
+ public void CheckStateValuesCanBeSet()
+ {
+ using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.ParseStateValues = true);
+ var logger = loggerFactory.CreateLogger();
+
+ logger.Log(
+ LogLevel.Information,
+ 0,
+ new List> { new KeyValuePair("Key1", "Value1") },
+ null,
+ (s, e) => "OpenTelemetry!");
+
+ var logRecord = exportedItems[0];
+ var expectedStateValues = new List> { new KeyValuePair("Key2", "Value2") };
+ logRecord.StateValues = expectedStateValues;
+
+ Assert.Equal(expectedStateValues, logRecord.StateValues);
+ }
+
+ [Fact]
+ public void CheckFormattedMessageCanBeSet()
+ {
+ using var loggerFactory = InitializeLoggerFactory(out List exportedItems, configure: options => options.IncludeFormattedMessage = true);
+ var logger = loggerFactory.CreateLogger();
+
+ logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World");
+ var logRecord = exportedItems[0];
+ var expectedFormattedMessage = "OpenTelemetry Good Night!";
+ logRecord.FormattedMessage = expectedFormattedMessage;
+
+ Assert.Equal(expectedFormattedMessage, logRecord.FormattedMessage);
+ }
+
+ [Fact]
+ public void CheckStateCanBeSetByProcessor()
+ {
+ var exportedItems = new List();
+ var exporter = new InMemoryExporter(exportedItems);
+ using var loggerFactory = LoggerFactory.Create(builder =>
+ {
+ builder.AddOpenTelemetry(options =>
+ {
+ options.AddProcessor(new RedactionProcessor(Field.State));
+ options.AddInMemoryExporter(exportedItems);
+ });
+ });
+
+ var logger = loggerFactory.CreateLogger();
+ logger.LogInformation($"This does not matter.");
+
+ var state = exportedItems[0].State as IReadOnlyList>;
+ Assert.Equal("newStateKey", state[0].Key.ToString());
+ Assert.Equal("newStateValue", state[0].Value.ToString());
+ }
+
+ [Fact]
+ public void CheckStateValuesCanBeSetByProcessor()
+ {
+ var exportedItems = new List();
+ var exporter = new InMemoryExporter(exportedItems);
+ using var loggerFactory = LoggerFactory.Create(builder =>
+ {
+ builder.AddOpenTelemetry(options =>
+ {
+ options.AddProcessor(new RedactionProcessor(Field.StateValues));
+ options.AddInMemoryExporter(exportedItems);
+ options.ParseStateValues = true;
+ });
+ });
+
+ var logger = loggerFactory.CreateLogger();
+ logger.LogInformation("This does not matter.");
+
+ var stateValue = exportedItems[0];
+ Assert.Equal(new KeyValuePair("newStateValueKey", "newStateValueValue"), stateValue.StateValues[0]);
+ }
+
+ [Fact]
+ public void CheckFormattedMessageCanBeSetByProcessor()
+ {
+ var exportedItems = new List();
+ var exporter = new InMemoryExporter(exportedItems);
+ using var loggerFactory = LoggerFactory.Create(builder =>
+ {
+ builder.AddOpenTelemetry(options =>
+ {
+ options.AddProcessor(new RedactionProcessor(Field.FormattedMessage));
+ options.AddInMemoryExporter(exportedItems);
+ options.IncludeFormattedMessage = true;
+ });
+ });
+
+ var logger = loggerFactory.CreateLogger();
+ logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World");
+
+ var item = exportedItems[0];
+ Assert.Equal("OpenTelemetry Good Night!", item.FormattedMessage);
+ }
+
[Fact]
public void CheckTraceIdForLogWithinDroppedActivity()
{
@@ -668,6 +791,32 @@ IEnumerator IEnumerable.GetEnumerator()
}
}
+ private class RedactionProcessor : BaseProcessor
+ {
+ private readonly Field fieldToUpdate;
+
+ public RedactionProcessor(Field fieldToUpdate)
+ {
+ this.fieldToUpdate = fieldToUpdate;
+ }
+
+ public override void OnEnd(LogRecord logRecord)
+ {
+ if (this.fieldToUpdate == Field.State)
+ {
+ logRecord.State = new List> { new KeyValuePair("newStateKey", "newStateValue") };
+ }
+ else if (this.fieldToUpdate == Field.StateValues)
+ {
+ logRecord.StateValues = new List> { new KeyValuePair("newStateValueKey", "newStateValueValue") };
+ }
+ else
+ {
+ logRecord.FormattedMessage = "OpenTelemetry Good Night!";
+ }
+ }
+ }
+
private class ListState : IEnumerable>
{
private readonly List> list;