Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exposed public setters for LogRecord.State, LogRecord.StateValues, and LogRecord.FormattedMessage. #3217

Merged
merged 6 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just for completion, we could show how to use same approach for logRecord.StateValues as well. (not a blocker for this PR)

{
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>");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it'd be good to modify the readme.md for the extending-the-sdk to include the redaction part. Similar to this https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/trace/extending-the-sdk#filtering-processor

}

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