Skip to content

Commit

Permalink
Exposed public setters for LogRecord.State, LogRecord.StateValues, an…
Browse files Browse the repository at this point in the history
…d LogRecord.FormattedMessage. (#3217)
  • Loading branch information
Yun-Ting authored Apr 22, 2022
1 parent ad2969d commit 11699a3
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 6 deletions.
53 changes: 53 additions & 0 deletions docs/logs/extending-the-sdk/MyClassWithRedactionEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// <copyright file="MyClassWithRedactionEnumerator.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System.Collections;
using System.Collections.Generic;

internal class MyClassWithRedactionEnumerator : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly IReadOnlyList<KeyValuePair<string, object>> state;

public MyClassWithRedactionEnumerator(IReadOnlyList<KeyValuePair<string, object>> state)
{
this.state = state;
}

public int Count => this.state.Count;

public KeyValuePair<string, object> this[int index] => this.state[index];

public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
foreach (var entry in this.state)
{
var entryVal = entry.Value;
if (entryVal != null && entryVal.ToString() != null && entryVal.ToString().Contains("<secret>"))
{
yield return new KeyValuePair<string, object>(entry.Key, "newRedactedValueHere");
}
else
{
yield return entry;
}
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
30 changes: 30 additions & 0 deletions docs/logs/extending-the-sdk/MyRedactionProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// <copyright file="MyRedactionProcessor.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

using System.Collections.Generic;
using OpenTelemetry;
using OpenTelemetry.Logs;

internal class MyRedactionProcessor : BaseProcessor<LogRecord>
{
public override void OnEnd(LogRecord logRecord)
{
if (logRecord.State is IReadOnlyList<KeyValuePair<string, object>> listOfKvp)
{
logRecord.State = new MyClassWithRedactionEnumerator(listOfKvp);
}
}
}
6 changes: 5 additions & 1 deletion docs/logs/extending-the-sdk/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -64,6 +65,9 @@ public static void Main()
{
logger.LogError("{name} is broken.", "refrigerator");
}

// message will be redacted by MyRedactionProcessor
logger.LogInformation("OpenTelemetry {sensitiveString}.", "<secret>");
}

internal struct Food
Expand Down
3 changes: 3 additions & 0 deletions src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
OpenTelemetry.Logs.LogRecord.State.set -> void
OpenTelemetry.Logs.LogRecord.StateValues.set -> void
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
OpenTelemetry.Logs.LogRecord.State.set -> void
OpenTelemetry.Logs.LogRecord.StateValues.set -> void
4 changes: 4 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions src/OpenTelemetry/Logs/LogRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,21 +81,21 @@ internal LogRecord(

public EventId EventId { get; }

public string FormattedMessage { get; }
public string FormattedMessage { get; set; }

/// <summary>
/// Gets the raw state attached to the log. Set to <see
/// Gets or sets the raw state attached to the log. Set to <see
/// langword="null"/> when <see
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled.
/// </summary>
public object State { get; }
public object State { get; set; }

/// <summary>
/// Gets the parsed state values attached to the log. Set when <see
/// Gets or sets the parsed state values attached to the log. Set when <see
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled
/// otherwise <see langword="null"/>.
/// </summary>
public IReadOnlyList<KeyValuePair<string, object>> StateValues { get; }
public IReadOnlyList<KeyValuePair<string, object>> StateValues { get; set; }

public Exception Exception { get; }

Expand Down
149 changes: 149 additions & 0 deletions test/OpenTelemetry.Tests/Logs/LogRecordTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ namespace OpenTelemetry.Logs.Tests
{
public sealed class LogRecordTest
{
private enum Field
{
FormattedMessage,
State,
StateValues,
}

[Fact]
public void CheckCategoryNameForLog()
{
Expand Down Expand Up @@ -243,6 +250,122 @@ public void CheckStateForExceptionLogged()
Assert.Equal(message, state.ToString());
}

[Fact]
public void CheckStateCanBeSet()
{
using var loggerFactory = InitializeLoggerFactory(out List<LogRecord> exportedItems, configure: null);
var logger = loggerFactory.CreateLogger<LogRecordTest>();

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<LogRecord> exportedItems, configure: options => options.ParseStateValues = true);
var logger = loggerFactory.CreateLogger<LogRecordTest>();

logger.Log(
LogLevel.Information,
0,
new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("Key1", "Value1") },
null,
(s, e) => "OpenTelemetry!");

var logRecord = exportedItems[0];
var expectedStateValues = new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("Key2", "Value2") };
logRecord.StateValues = expectedStateValues;

Assert.Equal(expectedStateValues, logRecord.StateValues);
}

[Fact]
public void CheckFormattedMessageCanBeSet()
{
using var loggerFactory = InitializeLoggerFactory(out List<LogRecord> exportedItems, configure: options => options.IncludeFormattedMessage = true);
var logger = loggerFactory.CreateLogger<LogRecordTest>();

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<LogRecord>();
var exporter = new InMemoryExporter<LogRecord>(exportedItems);
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.AddProcessor(new RedactionProcessor(Field.State));
options.AddInMemoryExporter(exportedItems);
});
});

var logger = loggerFactory.CreateLogger<LogRecordTest>();
logger.LogInformation($"This does not matter.");

var state = exportedItems[0].State as IReadOnlyList<KeyValuePair<string, object>>;
Assert.Equal("newStateKey", state[0].Key.ToString());
Assert.Equal("newStateValue", state[0].Value.ToString());
}

[Fact]
public void CheckStateValuesCanBeSetByProcessor()
{
var exportedItems = new List<LogRecord>();
var exporter = new InMemoryExporter<LogRecord>(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<LogRecordTest>();
logger.LogInformation("This does not matter.");

var stateValue = exportedItems[0];
Assert.Equal(new KeyValuePair<string, object>("newStateValueKey", "newStateValueValue"), stateValue.StateValues[0]);
}

[Fact]
public void CheckFormattedMessageCanBeSetByProcessor()
{
var exportedItems = new List<LogRecord>();
var exporter = new InMemoryExporter<LogRecord>(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<LogRecordTest>();
logger.LogInformation("OpenTelemetry {Greeting} {Subject}!", "Hello", "World");

var item = exportedItems[0];
Assert.Equal("OpenTelemetry Good Night!", item.FormattedMessage);
}

[Fact]
public void CheckTraceIdForLogWithinDroppedActivity()
{
Expand Down Expand Up @@ -668,6 +791,32 @@ IEnumerator IEnumerable.GetEnumerator()
}
}

private class RedactionProcessor : BaseProcessor<LogRecord>
{
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<KeyValuePair<string, object>> { new KeyValuePair<string, object>("newStateKey", "newStateValue") };
}
else if (this.fieldToUpdate == Field.StateValues)
{
logRecord.StateValues = new List<KeyValuePair<string, object>> { new KeyValuePair<string, object>("newStateValueKey", "newStateValueValue") };
}
else
{
logRecord.FormattedMessage = "OpenTelemetry Good Night!";
}
}
}

private class ListState : IEnumerable<KeyValuePair<string, object>>
{
private readonly List<KeyValuePair<string, object>> list;
Expand Down

0 comments on commit 11699a3

Please sign in to comment.