/* SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. * * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. 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. */ using System; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.Serialization; using OpenSearch.Net.Extensions; using OpenSearch.Net.Utf8Json; namespace OpenSearch.Net { internal class ExceptionFormatterResolver : IJsonFormatterResolver { public static readonly ExceptionFormatterResolver Instance = new ExceptionFormatterResolver(); private ExceptionFormatterResolver() { } public IJsonFormatter<T> GetFormatter<T>() { if (typeof(Exception).IsAssignableFrom(typeof(T))) { var type = typeof(ExceptionFormatter<>).MakeGenericType(typeof(T)); return (IJsonFormatter<T>)type.CreateInstance(); } return null; } } internal class ExceptionFormatter<TException> : IJsonFormatter<TException> where TException : Exception { private static List<Dictionary<string, object>> FlattenExceptions(Exception e) { var maxExceptions = 20; var exceptions = new List<Dictionary<string, object>>(maxExceptions); var depth = 0; do { var o = ToDictionary(e, depth); exceptions.Add(o); depth++; e = e.InnerException; } while (depth < maxExceptions && e != null); return exceptions; } private static Dictionary<string, object> ToDictionary(Exception e, int depth) { var o = new Dictionary<string, object>(10); var si = new SerializationInfo(e.GetType(), new FormatterConverter()); var sc = new StreamingContext(); e.GetObjectData(si, sc); var helpUrl = si.GetString("HelpURL"); var stackTrace = si.GetString("StackTraceString"); var remoteStackTrace = si.GetString("RemoteStackTraceString"); var remoteStackIndex = si.GetInt32("RemoteStackIndex"); var exceptionMethod = si.GetString("ExceptionMethod"); var hresult = si.GetInt32("HResult"); var source = si.GetString("Source"); var className = si.GetString("ClassName"); o.Add("Depth", depth); o.Add("ClassName", className); o.Add("Message", e.Message); o.Add("Source", source); o.Add("StackTraceString", stackTrace); o.Add("RemoteStackTraceString", remoteStackTrace); o.Add("RemoteStackIndex", remoteStackIndex); o.Add("HResult", hresult); o.Add("HelpURL", helpUrl); WriteStructuredExceptionMethod(o, exceptionMethod); return o; } private static void WriteStructuredExceptionMethod(Dictionary<string,object> o, string exceptionMethodString) { if (string.IsNullOrWhiteSpace(exceptionMethodString)) return; var args = exceptionMethodString.Split('\0', '\n'); if (args.Length != 5) return; var memberType = int.Parse(args[0], CultureInfo.InvariantCulture); var name = args[1]; var assemblyName = args[2]; var className = args[3]; var signature = args[4]; var an = new AssemblyName(assemblyName); var exceptionMethod = new Dictionary<string, object>(7) { { "Name", name }, { "AssemblyName", an.Name }, { "AssemblyVersion", an.Version.ToString() }, { "AssemblyCulture", an.CultureName }, { "ClassName", className }, { "Signature", signature }, { "MemberType", memberType } }; o.Add("ExceptionMethod", exceptionMethod); } public void Serialize(ref JsonWriter writer, TException value, IJsonFormatterResolver formatterResolver) { if (value == null) { writer.WriteNull(); return; } var flattenedExceptions = FlattenExceptions(value); var valueFormatter = formatterResolver.GetFormatter<object>(); writer.WriteBeginArray(); for (var i = 0; i < flattenedExceptions.Count; i++) { if (i > 0) writer.WriteValueSeparator(); var flattenedException = flattenedExceptions[i]; writer.WriteBeginObject(); var count = 0; foreach (var kv in flattenedException) { if (count > 0) writer.WriteValueSeparator(); writer.WritePropertyName(kv.Key); valueFormatter.Serialize(ref writer, kv.Value, formatterResolver); count++; } writer.WriteEndObject(); } writer.WriteEndArray(); } public TException Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) => throw new NotSupportedException(); } }