| File: Logging\JustInTimeRedactor.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.Telemetry\Microsoft.Extensions.Telemetry.csproj (Microsoft.Extensions.Telemetry) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Buffers; using System.Globalization; using System.Runtime.CompilerServices; using Microsoft.Extensions.Compliance.Redaction; using Microsoft.Extensions.ObjectPool; using Microsoft.Shared.Pools; namespace Microsoft.Extensions.Logging; /// <summary> /// Performs delayed redaction in order to avoid allocating intermediate strings and enabling redacting from span to span when possible. /// </summary> internal sealed class JustInTimeRedactor : IResettable #if NET6_0_OR_GREATER , ISpanFormattable #else , IFormattable #endif { private const int MaxStackAllocation = 256; private const int MaxDayOfYearLength = 3; private const string DiscriminatorSeparator = ":"; private Redactor? _redactor; private string _discriminator = string.Empty; private object? _value; public static JustInTimeRedactor Get(object? value, Redactor redactor, string discriminator) { var jr = _pool.Get(); jr._value = value; jr._redactor = redactor; jr._discriminator = discriminator; return jr; } public void Return() => _pool.Return(this); public JustInTimeRedactor? Next { get; set; } public override string ToString() => ToString(null, CultureInfo.InvariantCulture); [SkipLocalsInit] public string ToString(string? format, IFormatProvider? formatProvider) { if (_discriminator.Length == 0) { return _redactor!.Redact(_value, format, formatProvider); } _ = TryRedactWithDiscriminator(_value, [], out int _, format.AsSpan(), formatProvider, out var result, generateResult: true); return result; } public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { if (_discriminator.Length == 0) { return _redactor!.TryRedact(_value, destination, out charsWritten, format, provider); } return TryRedactWithDiscriminator(_value, destination, out charsWritten, format, provider, out string _, generateResult: false); } bool IResettable.TryReset() { _value = null; _redactor = null; _discriminator = string.Empty; return true; } [SkipLocalsInit] private bool TryRedactWithDiscriminator(object? value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? formatProvider, out string result, bool generateResult) { Span<char> workBuffer = stackalloc char[MaxStackAllocation]; int charsInWorkBuffer = 0; char[]? a = null; // first step is to acquire the actual text to be redacted #if NET6_0_OR_GREATER bool done = false; if (value is ISpanFormattable sf) { done = sf.TryFormat(workBuffer, out charsInWorkBuffer, format, formatProvider); } if (done) { int maxLenToRedact = charsInWorkBuffer + DiscriminatorSeparator.Length + _discriminator.Length + MaxDayOfYearLength; if (maxLenToRedact > workBuffer.Length) { a = ArrayPool<char>.Shared.Rent(maxLenToRedact); workBuffer.Slice(0, charsInWorkBuffer).CopyTo(a); workBuffer = a; } } else #endif #pragma warning disable S1199 { #pragma warning restore S1199 ReadOnlySpan<char> inputAsSpan = default; if (value is IFormattable f) { inputAsSpan = f.ToString(format.Length > 0 ? format.ToString() : string.Empty, formatProvider).AsSpan(); } else if (value is char[] c) { // An attempt to call value.ToString() on a char[] will produce the string "System.Char[]" and redaction will be attempted on it, // instead of the provided array. inputAsSpan = c.AsSpan(); } else { var str = value?.ToString() ?? string.Empty; inputAsSpan = str.AsSpan(); } int maxLenToRedact = inputAsSpan.Length + DiscriminatorSeparator.Length + _discriminator.Length + MaxDayOfYearLength; if (maxLenToRedact > workBuffer.Length) { a = ArrayPool<char>.Shared.Rent(maxLenToRedact); workBuffer = a; } inputAsSpan.CopyTo(workBuffer); charsInWorkBuffer = inputAsSpan.Length; } // next step is to add the discriminator DiscriminatorSeparator.AsSpan().CopyTo(workBuffer.Slice(charsInWorkBuffer)); charsInWorkBuffer += DiscriminatorSeparator.Length; _discriminator.AsSpan().CopyTo(workBuffer.Slice(charsInWorkBuffer)); charsInWorkBuffer += _discriminator.Length; // final step is to invoke the actual redactor var finalToBeRedacted = workBuffer.Slice(0, charsInWorkBuffer); result = string.Empty; int redactedLen = _redactor!.GetRedactedLength(finalToBeRedacted); if (redactedLen > destination.Length) { if (generateResult) { var t = ArrayPool<char>.Shared.Rent(redactedLen); _redactor!.Redact(finalToBeRedacted, t); result = t.AsSpan().Slice(0, redactedLen).ToString(); ArrayPool<char>.Shared.Return(t); } charsWritten = 0; if (a != null) { ArrayPool<char>.Shared.Return(a); } return false; } charsWritten = _redactor!.Redact(finalToBeRedacted, destination); if (a != null) { ArrayPool<char>.Shared.Return(a); } return true; } private static readonly ObjectPool<JustInTimeRedactor> _pool = PoolFactory.CreateResettingPool<JustInTimeRedactor>(); }