Exposed public setters for LogRecord.State, LogRecord.StateValues, and LogRecord.FormattedMessage. (#3217)
This commit is contained in:
parent
ad2969df88
commit
11699a351e
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
|
||||
OpenTelemetry.Logs.LogRecord.State.set -> void
|
||||
OpenTelemetry.Logs.LogRecord.StateValues.set -> void
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
OpenTelemetry.Logs.LogRecord.FormattedMessage.set -> void
|
||||
OpenTelemetry.Logs.LogRecord.State.set -> void
|
||||
OpenTelemetry.Logs.LogRecord.StateValues.set -> void
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue