File 3501-Add-support-for-primary-metadata-to-the-logger.patch of Package erlang

From 576b69ac6e55ad29a83b6b0bb1ce17040492625f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Niemier?= <lukasz@niemier.pl>
Date: Thu, 14 Nov 2019 14:46:27 +0100
Subject: [PATCH] Add support for primary metadata to the logger

This adds new `kernel` configuration option `logger_metadata`
that will contain default metadata.

Resolves #4105
---
 lib/kernel/doc/src/kernel_app.xml     |  6 ++++
 lib/kernel/doc/src/logger.xml         | 29 +++++++++++-----
 lib/kernel/doc/src/logger_chapter.xml | 29 +++++++++++++++-
 lib/kernel/src/logger.erl             | 48 ++++++++++++++++++++-------
 lib/kernel/src/logger_backend.erl     | 24 +++++++-------
 lib/kernel/src/logger_config.erl      |  3 ++
 lib/kernel/src/logger_server.erl      |  3 ++
 lib/kernel/test/logger_SUITE.erl      | 16 ++++++++-
 8 files changed, 124 insertions(+), 34 deletions(-)

diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index eb95b9b733..c17b327f32 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -200,6 +200,12 @@
             <c>logger:set_primary_config(level, Level)</c></seemfa>.</p>
 	<p>Defaults to <c>notice</c>.</p>
       </item>
+      <tag><marker id="logger_metadata"/><c>logger_metadata = Metadata</c></tag>
+      <item>
+	<p>Specifies primary metadata for log events.</p>
+	<p><c>Metadata = map()</c></p>
+	<p>Defaults to <c>#{}</c>.</p>
+      </item>
       <tag><marker id="logger_sasl_compatible"/>
 	<c>logger_sasl_compatible = true | false</c></tag>
       <item>
diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml
index 801a2c1193..2f76dbd567 100644
--- a/lib/kernel/doc/src/logger.xml
+++ b/lib/kernel/doc/src/logger.xml
@@ -199,17 +199,28 @@ logger:error("error happened because: ~p", [Reason]). % Without macro
 	  <item><c>file => ?FILE</c></item>
 	  <item><c>line => ?LINE</c></item>
 	</list>
-	<p>You can add custom metadata, either by specifying a map as
-	  the last parameter to any of the log macros or the API
-	  functions, or by setting process metadata
-	  with <seemfa marker="#set_process_metadata/1">
+	<p>You can add custom metadata, either by:</p>
+        <list>
+          <item>specifying a map as the last parameter to any of the
+            log macros or the logger API functions.</item>
+          <item>setting process metadata with <seemfa marker="#set_process_metadata/1">
 	    <c>set_process_metadata/1</c></seemfa>
-	  or <seemfa marker="#update_process_metadata/1">
-	    <c>update_process_metadata/1</c></seemfa>.</p>
+	    or <seemfa marker="#update_process_metadata/1">
+	  <c>update_process_metadata/1</c></seemfa>.</item>
+          <item>setting primary metadata with <seemfa marker="#set_primary_config/1">
+            <c>set_primary_config/1</c></seemfa> or through the kernel
+            configuration parameter <seeapp marker="kernel_app#logger_metadata">
+            logger_metadata</seeapp></item>
+        </list>
+        <note>
+          <p>When adding custom metadata, make sure not to use any of the keys
+            mentioned above as that may cause a lot of confusion about the log
+            events.</p>
+        </note>
 	<p>Logger merges all the metadata maps before forwarding the
 	  log event to the handlers. If the same keys occur, values
-	  from the log call overwrite process metadata, which in turn
-	  overwrite values set by Logger.</p>
+	  from the log call overwrite process metadata, which overwrites
+          the primary metadata, which in turn overwrite values set by Logger.</p>
 	<p>The following custom metadata keys have special meaning:</p>
 	<taglist>
 	  <tag><c>domain</c></tag>
@@ -816,10 +827,12 @@ start(_, []) ->
       <name name="set_primary_config" arity="2" clause_i="1" since="OTP 21.0"/>
       <name name="set_primary_config" arity="2" clause_i="2" since="OTP 21.0"/>
       <name name="set_primary_config" arity="2" clause_i="3" since="OTP 21.0"/>
+      <name name="set_primary_config" arity="2" clause_i="4" since="OTP 24.0"/>
       <fsummary>Add or update primary configuration data for Logger.</fsummary>
       <type variable="Level" name_i="1"/>
       <type variable="FilterDefault" name_i="2"/>
       <type variable="Filters" name_i="3"/>
+      <type variable="Meta" name_i="4"/>
       <desc>
         <p>Add or update primary configuration data for Logger. If the
           given <c><anno>Key</anno></c> already exists, its associated
diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml
index b082607751..e2782ad652 100644
--- a/lib/kernel/doc/src/logger_chapter.xml
+++ b/lib/kernel/doc/src/logger_chapter.xml
@@ -237,8 +237,21 @@ logger:debug(#{got => connection_request, id => Id, state => State},
       <title>Metadata</title>
       <p>Metadata contains additional data associated with a log
 	message. Logger inserts some metadata fields by default, and
-	the client can add custom metadata in two different ways:</p>
+	the client can add custom metadata in three different ways:</p>
       <taglist>
+        <tag>Set primary metadata</tag>
+        <item>
+          <p>Primary metadata applies is the base metadata given to
+            all log events. At startup it can be set using
+            the kernel configuration parameter
+            <seeapp marker="kernel_app#logger_metadata">logger_metadata</seeapp>.
+            At run-time it can be set and updated using
+            <seemfa marker="logger#set_primary_config/1">
+              <c>logger:set_primary_config/1</c></seemfa> and
+            <seemfa marker="logger#update_primary_config/1">
+              <c>logger:update_primary_config/1</c></seemfa> respectively.
+          </p>
+        </item>
 	<tag>Set process metadata</tag>
 	<item>
 	  <p>Process metadata is set and updated
@@ -544,6 +557,13 @@ logger:debug(#{got => connection_request, id => Id, state => State},
 	    for more information about how this option is used.</p>
 	  <p>Defaults to <c>log</c>.</p>
 	</item>
+        <tag><c>metadata = </c><seetype marker="logger#metadata"><c>metadata()</c></seetype></tag>
+	<item>
+	  <p>The primary metadata to be used for all log calls.</p>
+          <p>See section <seeguide marker="#metadata">Metadata</seeguide>
+	    for more information about how this option is used.</p>
+	  <p>Defaults to <c>#{}</c>.</p>
+	</item>
       </taglist>
     </section>
 
@@ -762,6 +782,13 @@ logger:debug(#{got => connection_request, id => Id, state => State},
 	  Examples</seeguide> for examples using the <c>logger</c>
 	  parameter for system configuration.</p>
       </item>
+      <tag><marker id="logger_metadata"/>
+	<c>logger_metadata = map()</c></tag>
+      <item>
+	<p>Specifies the primary metadata. See
+	  the <seeapp marker="kernel_app#logger_metadata"><c>kernel(6)</c></seeapp>
+	  manual page for more information about this parameter.</p>
+      </item>
       <tag><marker id="logger_level"/>
 	<c>logger_level = Level</c></tag>
       <item>
diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl
index 167637b8b4..29fac2427d 100644
--- a/lib/kernel/src/logger.erl
+++ b/lib/kernel/src/logger.erl
@@ -108,6 +108,7 @@
 -type filter_arg() :: term().
 -type filter_return() :: stop | ignore | log_event().
 -type primary_config() :: #{level => level() | all | none,
+                            metadata => metadata(),
                             filter_default => log | stop,
                             filters => [{filter_id(),filter()}]}.
 -type handler_config() :: #{id => handler_id(),
@@ -403,7 +404,9 @@ remove_handler(HandlerId) ->
                         (filter_default,FilterDefault) -> ok | {error,term()} when
       FilterDefault :: log | stop;
                         (filters,Filters) -> ok | {error,term()} when
-      Filters :: [{filter_id(),filter()}].
+      Filters :: [{filter_id(),filter()}];
+                        (metadata,Meta) -> ok | {error,term()} when
+      Meta :: metadata().
 set_primary_config(Key,Value) ->
     logger_server:set_config(primary,Key,Value).
 
@@ -799,6 +802,7 @@ internal_init_logger() ->
         Env = get_logger_env(kernel),
         check_logger_config(kernel,Env),
         ok = logger:set_primary_config(level, get_logger_level()),
+        ok = logger:set_primary_config(metadata, get_primary_metadata()),
         ok = logger:set_primary_config(filter_default,
                                        get_primary_filter_default(Env)),
 
@@ -955,6 +959,14 @@ get_logger_level() ->
             throw({logger_level, Level})
     end.
 
+get_primary_metadata() ->
+    case application:get_env(kernel,logger_default_metadata,#{}) of
+        Meta when is_map(Meta) ->
+            Meta;
+        Meta ->
+            throw({logger_metadata, Meta})
+    end.
+
 get_primary_filter_default(Env) ->
     case lists:keyfind(filters,1,Env) of
         {filters,Default,_} ->
@@ -1067,40 +1079,52 @@ log_allowed(Location,Level,{Fun,FunArgs},Meta) when is_function(Fun,1) ->
                          [{Fun,FunArgs},{C,R}]},
                         Meta)
     end;
-log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) ->
+log_allowed(Location,Level,Msg,LogCallMeta) when is_map(LogCallMeta) ->
+
+    Tid = tid(),
+
+    {ok,PrimaryConfig} = logger_config:get(Tid,primary),
+
     %% Metadata priorities are:
-    %% Location (added in API macros) - will be overwritten by process
-    %% metadata (set by set_process_metadata/1), which in turn will be
+    %% Location (added in API macros) - will be overwritten by
+    %% Default Metadata (added by logger:primary_config/1) - will be overwritten by
+    %% process metadata (set by set_process_metadata/1), which in turn will be
     %% overwritten by the metadata given as argument in the log call
     %% (function or macro).
+
     Meta = add_default_metadata(
-             maps:merge(Location,maps:merge(proc_meta(),Meta0))),
+             maps:merge(
+               Location,
+               maps:merge(
+                 maps:get(metadata,PrimaryConfig),
+                 maps:merge(proc_meta(),LogCallMeta)))),
+
     case node(maps:get(gl,Meta)) of
         Node when Node=/=node() ->
             log_remote(Node,Level,Msg,Meta);
         _ ->
             ok
     end,
-    do_log_allowed(Level,Msg,Meta,tid()).
+    do_log_allowed(Level,Msg,Meta,Tid,PrimaryConfig).
 
-do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid)
+do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid,Config)
   when ?IS_LEVEL(Level),
        ?IS_FORMAT(Format),
        is_list(Args),
        is_map(Meta) ->
-    logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid);
-do_log_allowed(Level,Report,Meta,Tid)
+    logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid,Config);
+do_log_allowed(Level,Report,Meta,Tid,Config)
   when ?IS_LEVEL(Level),
        ?IS_REPORT(Report),
        is_map(Meta) ->
     logger_backend:log_allowed(#{level=>Level,msg=>{report,Report},meta=>Meta},
-                               Tid);
-do_log_allowed(Level,String,Meta,Tid)
+                               Tid,Config);
+do_log_allowed(Level,String,Meta,Tid,Config)
   when ?IS_LEVEL(Level),
        ?IS_STRING(String),
        is_map(Meta) ->
     logger_backend:log_allowed(#{level=>Level,msg=>{string,String},meta=>Meta},
-                               Tid).
+                               Tid,Config).
 tid() ->
     ets:whereis(?LOGGER_TABLE).
 
diff --git a/lib/kernel/src/logger_backend.erl b/lib/kernel/src/logger_backend.erl
index 7003d2268e..682d0452bd 100644
--- a/lib/kernel/src/logger_backend.erl
+++ b/lib/kernel/src/logger_backend.erl
@@ -19,7 +19,7 @@
 %%
 -module(logger_backend).
 
--export([log_allowed/2]).
+-export([log_allowed/3]).
 
 -include("logger_internal.hrl").
 
@@ -27,28 +27,28 @@
 
 %%%-----------------------------------------------------------------
 %%% The default logger backend
-log_allowed(Log, Tid) ->
-    {ok,Config} = logger_config:get(Tid,primary),
-    Filters = maps:get(filters,Config,[]),
-    case apply_filters(primary,Log,Filters,Config) of
+log_allowed(Log, Tid, PrimaryConfig) ->
+    Filters = maps:get(filters,PrimaryConfig,[]),
+    case apply_filters(primary,Log,Filters,PrimaryConfig) of
         stop ->
             ok;
         Log1 ->
-            Handlers = maps:get(handlers,Config,[]),
+            Handlers = maps:get(handlers,PrimaryConfig,[]),
             call_handlers(Log1,Handlers,Tid)
     end,
     ok.
 
 call_handlers(#{level:=Level}=Log,[Id|Handlers],Tid) ->
+    %% Get the config for handle Id
     case logger_config:get(Tid,Id,Level) of
-        {ok,#{module:=Module}=Config} ->
-            Filters = maps:get(filters,Config,[]),
-            case apply_filters(Id,Log,Filters,Config) of
+        {ok,#{module:=Module}=HandlerConfig} ->
+            Filters = maps:get(filters,HandlerConfig,[]),
+            case apply_filters(Id,Log,Filters,HandlerConfig) of
                 stop ->
                     ok;
                 Log1 ->
-                    Config1 = maps:without(?OWN_KEYS,Config),
-                    try Module:log(Log1,Config1)
+                    HandlerConfig1 = maps:without(?OWN_KEYS,HandlerConfig),
+                    try Module:log(Log1,HandlerConfig1)
                     catch C:R:S ->
                             case logger:remove_handler(Id) of
                                 ok ->
@@ -60,7 +60,7 @@ call_handlers(#{level:=Level}=Log,[Id|Handlers],Tid) ->
                                        [{logger,removed_failing_handler},
                                         {handler,{Id,Module}},
                                         {log_event,Log1},
-                                        {config,Config1},
+                                        {config,HandlerConfig1},
                                         {reason,{C,R,filter_stacktrace(S)}}]);
                                 {error,{not_found,_}} ->
                                     %% Probably already removed by other client
diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl
index 9e94fdc6a1..4bf11aa38c 100644
--- a/lib/kernel/src/logger_config.erl
+++ b/lib/kernel/src/logger_config.erl
@@ -122,9 +122,12 @@ set(Tid,proxy,Config) ->
     ok;
 set(Tid,What,Config) ->
     LevelInt = level_to_int(maps:get(level,Config)),
+    %% Put either primary or handler level in pt cache
     ok = persistent_term:put({?MODULE,table_key(What)}, LevelInt),
     case What of
         primary ->
+            %% If we change primary level, then we need to flush
+            %% the module level cache.
             [persistent_term:put(Key,?PRIMARY_TO_CACHE(LevelInt))
              || {{?MODULE,Module} = Key,Level} <- persistent_term:get(),
                 ?IS_MODULE(Module), ?IS_CACHED(Level)],
diff --git a/lib/kernel/src/logger_server.erl b/lib/kernel/src/logger_server.erl
index 6bdac471ec..942c0aac5a 100644
--- a/lib/kernel/src/logger_server.erl
+++ b/lib/kernel/src/logger_server.erl
@@ -415,6 +415,7 @@ do_remove_filter(Tid,Id,FilterId) ->
 
 default_config(primary) ->
     #{level=>notice,
+      metadata=>#{},
       filters=>[],
       filter_default=>log};
 default_config(Id) ->
@@ -461,6 +462,8 @@ check_config(Owner,[{filter_default,FD}|Config]) ->
 check_config(handler,[{formatter,Formatter}|Config]) ->
     check_formatter(Formatter),
     check_config(handler,Config);
+check_config(primary,[{metadata,Meta}|Config]) when is_map(Meta) ->
+    check_config(primary,Config);
 check_config(primary,[C|_]) ->
     throw({invalid_primary_config,C});
 check_config(handler,[{_,_}|Config]) ->
diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl
index 8513cf2936..6165962cf9 100644
--- a/lib/kernel/test/logger_SUITE.erl
+++ b/lib/kernel/test/logger_SUITE.erl
@@ -274,7 +274,7 @@ change_config(_Config) ->
     ok = logger:set_primary_config(#{filter_default=>stop}),
     #{level:=notice,filters:=[],filter_default:=stop}=PC1 =
         logger:get_primary_config(),
-    3 = maps:size(PC1),
+    4 = maps:size(PC1),
     %% Check that internal 'handlers' field has not been changed
     MS = [{{{?HANDLER_KEY,'$1'},'_'},[],['$1']}],
     HIds1 = lists:sort(ets:select(?LOGGER_TABLE,MS)), % dirty, internal data
@@ -918,6 +918,20 @@ process_metadata(_Config) ->
     logger:notice(S3=?str,#{custom=>func}),
     check_logged(notice,S3,#{time=>Time,line=>0,custom=>func}),
 
+    %% Test that primary metadata is overwritten by process metadata
+    ok = logger:update_primary_config(
+           #{metadata=>#{time=>Time,custom=>global,global=>added,line=>1}}),
+    logger:notice(S4=?str),
+    check_logged(notice,S4,#{time=>Time,line=>0,custom=>proc,global=>added}),
+
+    %% Test that primary metadata is overwritten by func metadata
+    %% and that primary overwrites location metadata.
+    ok = logger:unset_process_metadata(),
+    logger:notice(S5=?str,#{custom=>func}),
+    check_logged(notice,S5,#{time=>Time,line=>1,custom=>func,global=>added}),
+    ok = logger:set_process_metadata(ProcMeta),
+    ok = logger:update_primary_config(#{metadata=>#{}}),
+
     ProcMeta = logger:get_process_metadata(),
     ok = logger:update_process_metadata(#{custom=>changed,custom2=>added}),
     Expected = ProcMeta#{custom:=changed,custom2=>added},
-- 
2.26.2

openSUSE Build Service is sponsored by