33using Microsoft . Extensions . Logging ;
44using System ;
55using System . ComponentModel ;
6+ using System . Security . Claims ;
67using System . Text ;
78
89namespace Olive
910{
1011 public static class LogExtensions
1112 {
12- public static ILoggingBuilder AddFile ( this ILoggingBuilder @this , Action < FileLoggerOptions > configure = null )
13+ public static ILoggingBuilder AddFile ( this ILoggingBuilder @this , Action < FileLoggerOptions > configure = null )
1314 {
14- @this . Services . AddSingleton < ILoggerProvider , FileLoggerProvider > ( ) ;
15- if ( configure != null ) @this . Services . Configure ( configure ) ;
15+ @this . Services . AddSingleton < ILoggerProvider , FileLoggerProvider > ( ) ;
16+ if ( configure != null ) @this . Services . Configure ( configure ) ;
1617 return @this ;
1718 }
1819
@@ -53,7 +54,7 @@ static string ToYaml(string description, object relatedObject, string userId, st
5354 {
5455 r . Append ( " Description: " ) ;
5556 var firstLine = true ;
56-
57+
5758 foreach ( var line in description . ToLines ( ) . Trim ( ) )
5859 {
5960 if ( ! firstLine ) r . Append ( " Description: " . Length ) ;
@@ -70,6 +71,11 @@ public static class Log
7071 {
7172 public static ILoggerFactory Factory { get ; private set ; }
7273
74+ /// <summary>
75+ /// When set, provides contextual information (e.g. UserId, RequestUrl, UserIP) to append to log entries.
76+ /// </summary>
77+ public static Func < string > ContextProvider { get ; set ; }
78+
7379 [ EditorBrowsable ( EditorBrowsableState . Never ) ]
7480 public static void Init ( Action < ILoggingBuilder > configure = null )
7581 {
@@ -97,6 +103,57 @@ public static void Init(ILoggerFactory factory)
97103 {
98104 if ( Factory != null ) return ;
99105 Factory = factory ;
106+ InitDefaultContextProvider ( ) ;
107+ }
108+
109+ static void InitDefaultContextProvider ( )
110+ {
111+ var httpContextAccessorType = Type . GetType (
112+ "Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.Abstractions" ) ;
113+
114+ if ( httpContextAccessorType == null ) return ;
115+
116+ var httpContextProp = httpContextAccessorType . GetProperty ( "HttpContext" ) ;
117+
118+ ContextProvider = ( ) =>
119+ {
120+ try
121+ {
122+ var accessor = Context . Current . GetOptionalService ( httpContextAccessorType ) ;
123+ if ( accessor == null ) return null ;
124+
125+ var httpContext = httpContextProp ? . GetValue ( accessor ) ;
126+ if ( httpContext == null ) return null ;
127+
128+ var contextType = httpContext . GetType ( ) ;
129+
130+ var user = contextType . GetProperty ( "User" ) ? . GetValue ( httpContext ) as ClaimsPrincipal ;
131+ var userId = user ? . GetId ( ) ;
132+
133+ var request = contextType . GetProperty ( "Request" ) ? . GetValue ( httpContext ) ;
134+ string requestUrl = null ;
135+ if ( request != null )
136+ {
137+ var reqType = request . GetType ( ) ;
138+ var pathBase = reqType . GetProperty ( "PathBase" ) ? . GetValue ( request ) ? . ToString ( ) ;
139+ var path = reqType . GetProperty ( "Path" ) ? . GetValue ( request ) ? . ToString ( ) ;
140+ var queryString = reqType . GetProperty ( "QueryString" ) ? . GetValue ( request ) ? . ToString ( ) ;
141+ requestUrl = $ "{ pathBase } { path } { queryString } ";
142+ }
143+
144+ var connection = contextType . GetProperty ( "Connection" ) ? . GetValue ( httpContext ) ;
145+ var userIp = connection ? . GetType ( ) . GetProperty ( "RemoteIpAddress" ) ? . GetValue ( connection ) ? . ToString ( ) ;
146+
147+ if ( userId . IsEmpty ( ) && requestUrl . IsEmpty ( ) && userIp . IsEmpty ( ) ) return null ;
148+
149+ var r = new StringBuilder ( ) ;
150+ if ( userId . HasValue ( ) ) r . AppendLine ( $ " UserId: { userId } ") ;
151+ if ( requestUrl . HasValue ( ) ) r . AppendLine ( $ " RequestUrl: { requestUrl } ") ;
152+ if ( userIp . HasValue ( ) ) r . AppendLine ( $ " UserIP: { userIp } ") ;
153+ return r . ToString ( ) . TrimEnd ( ) ;
154+ }
155+ catch { return null ; }
156+ } ;
100157 }
101158 public static bool AddProvider < TProvider > ( ) where TProvider : ILoggerProvider
102159 {
0 commit comments