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

This commit is contained in:
Yun-Ting Lin 2022-04-21 22:02:47 -07:00 committed by GitHub
parent ad2969df88
commit 11699a351e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 252 additions and 6 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -28,7 +28,8 @@ public class Program
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 class Program
{
logger.LogError("{name} is broken.", "refrigerator");
}
// message will be redacted by MyRedactionProcessor
logger.LogInformation("OpenTelemetry {sensitiveString}.", "<secret>");
}
internal struct Food

View File

@ -0,0 +1,3 @@
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
OpenTelemetry.Logs.LogRecord.State.set -> void
OpenTelemetry.Logs.LogRecord.StateValues.set -> void

View File

@ -0,0 +1,3 @@
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
OpenTelemetry.Logs.LogRecord.State.set -> void
OpenTelemetry.Logs.LogRecord.StateValues.set -> void

View File

@ -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

View File

@ -81,21 +81,21 @@ namespace OpenTelemetry.Logs
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; }

View File

@ -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 @@ namespace OpenTelemetry.Logs.Tests
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()
{
@ -668,6 +791,32 @@ namespace OpenTelemetry.Logs.Tests
}
}
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;