blob: 7d365734ef29539ad52ccef1374ead1c5105f833 [file] [log] [blame]
#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
//
#endregion
using System;
using System.IO;
using System.Collections.Concurrent;
using log4net.Util;
namespace log4net.ObjectRenderer;
/// <summary>
/// Maps types to <see cref="IObjectRenderer"/> instances for types that require custom
/// rendering.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="FindAndRender(object)"/> method is used to render an
/// <see langword="object"/> using the appropriate renderers defined in this map,
/// using a default renderer if no custom renderer is defined for a type.
/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
public class RendererMap
{
private static readonly Type _declaringType = typeof(RendererMap);
private readonly ConcurrentDictionary<Type, IObjectRenderer> _map = [];
private readonly ConcurrentDictionary<Type, IObjectRenderer> _cache = [];
/// <summary>
/// Renders <paramref name="obj"/> using the appropriate renderer.
/// </summary>
/// <param name="obj">the object to render to a string</param>
/// <returns>The object rendered as a string.</returns>
/// <remarks>
/// <para>
/// This is a convenience method used to render an object to a string.
/// The alternative method <see cref="FindAndRender(object,TextWriter)"/>
/// should be used when streaming output to a <see cref="TextWriter"/>.
/// </para>
/// </remarks>
public string FindAndRender(object? obj)
{
// Optimisation for strings
if (obj is string strData)
{
return strData;
}
using StringWriter stringWriter = new(System.Globalization.CultureInfo.InvariantCulture);
FindAndRender(obj, stringWriter);
return stringWriter.ToString();
}
/// <summary>
/// Render <paramref name="obj"/> using the appropriate renderer.
/// </summary>
/// <param name="obj">the object to render to a string</param>
/// <param name="writer">The writer to render to</param>
/// <remarks>
/// <para>
/// Find the appropriate renderer for the type of the
/// <paramref name="obj"/> parameter. This is accomplished by calling the
/// <see cref="Get(Type)"/> method. Once a renderer is found, it is
/// applied on the object <paramref name="obj"/> and the result is returned
/// as a <see cref="string"/>.
/// </para>
/// </remarks>
public void FindAndRender(object? obj, TextWriter writer)
{
writer.EnsureNotNull();
if (obj is null)
{
writer.Write(SystemInfo.NullText);
}
else
{
// Optimisation for strings
if (obj is string str)
{
writer.Write(str);
}
else
{
// Lookup the renderer for the specific type
try
{
Get(obj.GetType()).RenderObject(this, obj, writer);
}
catch (Exception e) when (!e.IsFatal())
{
// Exception rendering the object
LogLog.Error(_declaringType, $"Exception while rendering object of type [{obj.GetType().FullName}]", e);
// return default message
string objectTypeName = obj.GetType().FullName ?? string.Empty;
writer.Write($"<log4net.Error>Exception rendering object type [{objectTypeName}]");
string? exceptionText = null;
try
{
exceptionText = e.ToString();
}
catch (Exception inner) when (!inner.IsFatal())
{
// Ignore exception
}
writer.Write($"<stackTrace>{exceptionText}</stackTrace>");
writer.Write("</log4net.Error>");
}
}
}
}
/// <summary>
/// Gets the renderer for the specified object type.
/// </summary>
/// <param name="obj">The object for which to look up the renderer.</param>
/// <returns>the renderer for <paramref name="obj"/></returns>
/// <remarks>
/// <param>
/// Gets the renderer for the specified object type.
/// </param>
/// <param>
/// Syntactic sugar method that calls <see cref="Get(Type)"/>
/// with the type of the object parameter.
/// </param>
/// </remarks>
public IObjectRenderer? Get(object? obj)
{
if (obj is null)
{
return null;
}
return Get(obj.GetType());
}
/// <summary>
/// Gets the renderer for the specified type
/// </summary>
/// <param name="type">the type to look up the renderer for</param>
/// <returns>The renderer for the specified type, or <see cref="DefaultRenderer"/> if no specific renderer has been defined.</returns>
public IObjectRenderer Get(Type type)
{
// Check cache
if (!_cache.TryGetValue(type.EnsureNotNull(), out IObjectRenderer? result))
{
for (Type? cur = type; cur is not null; cur = cur.BaseType)
{
// Search the type's interfaces
result = SearchTypeAndInterfaces(cur);
if (result is not null)
{
break;
}
}
// if not set then use the default renderer
result ??= DefaultRenderer;
// Add to cache
_cache.TryAdd(type, result);
}
return result;
}
/// <summary>
/// Recursively searches interfaces.
/// </summary>
/// <param name="type">The type for which to look up the renderer.</param>
/// <returns>The renderer for the specified type, or <see langword="null"/> if not found.</returns>
private IObjectRenderer? SearchTypeAndInterfaces(Type type)
{
if (_map.TryGetValue(type, out IObjectRenderer? r))
{
return r;
}
foreach (Type t in type.GetInterfaces())
{
r = SearchTypeAndInterfaces(t);
if (r is not null)
{
return r;
}
}
return null;
}
/// <summary>
/// Gets the default renderer instance
/// </summary>
public static IObjectRenderer DefaultRenderer { get; } = new DefaultRenderer();
/// <summary>
/// Clears the map of custom renderers. The <see cref="DefaultRenderer"/>
/// is not removed.
/// </summary>
public void Clear()
{
_map.Clear();
_cache.Clear();
}
/// <summary>
/// Registers an <see cref="IObjectRenderer"/> for <paramref name="typeToRender"/>.
/// </summary>
/// <param name="typeToRender">The type that will be rendered by <paramref name="renderer"/>.</param>
/// <param name="renderer">The renderer for <paramref name="typeToRender"/>.</param>
public void Put(Type typeToRender, IObjectRenderer renderer)
{
_cache.Clear();
_map[typeToRender.EnsureNotNull()] = renderer.EnsureNotNull();
}
}