PageSourceSearch

https://armeria.dev/assets/js/23dad7f2.262654ca.js

js armeria.dev collected 2026-10-03 23:25:20 UTC 15,094 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkarmeria_site=self.webpackChunkarmeria_site||[]).push([["4569"],{78171(e,n,r){r.r(n),r.d(n,{metadata:()=>t,default:()=>p,frontMatter:()=>i,contentTitle:()=>s,toc:()=>l,assets:()=>c});var t=JSON.parse('{"id":"advanced/logging","title":"Logging contextual information","description":"With Armeria\'s Logback integration, you can log the properties of the","source":"@site/src/content/docs/advanced/logging.mdx","sourceDirName":"advanced","slug":"/advanced/logging","permalink":"/docs/advanced/logging","draft":false,"unlisted":false,"editUrl":"https://github.com/line/armeria/edit/main/site/src/content/docs/advanced/logging.mdx","tags":[],"version":"current","frontMatter":{},"sidebar":"docsSidebar","previous":{"title":"Advanced","permalink":"/docs/category/advanced"},"next":{"title":"Structured logging","permalink":"/docs/advanced/structured-logging"}}'),o=r(74848),a=r(28453);let i={},s="Logging contextual information",c={},l=[{value:"Built-in properties",id:"built-in-properties",level:2},{value:"HTTP request and response headers",id:"http-request-and-response-headers",level:2},{value:"Custom attributes",id:"custom-attributes",level:2},{value:"Using an alternative string converter for a custom attribute",id:"using-an-alternative-string-converter-for-a-custom-attribute",level:3},{value:"Customizing MDC keys",id:"customizing-mdc-keys",level:2},{value:"Specifying a prefix for MDC keys",id:"specifying-a-prefix-for-mdc-keys",level:3}];function d(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,a.R)(),...e.components},{RequiredDependencies:r}=n;return r||function(e,n){throw Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("RequiredDependencies",!0),(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"logging-contextual-information",children:"Logging contextual information"})}),"\n",(0,o.jsxs)(n.p,{children:["With Armeria's ",(0,o.jsx)(n.a,{href:"https://logback.qos.ch/",children:"Logback"})," integration, you can log the properties of the\n",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/RequestContext.html",children:"RequestContext"})," of the request being handled. ",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/logback/RequestContextExportingAppender.html",children:"RequestContextExportingAppender"})," is\na Logback appender that exports the properties of the current ",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/RequestContext.html",children:"RequestContext"})," to\n",(0,o.jsx)(n.a,{href:"https://logback.qos.ch/manual/mdc.html",children:"MDC"})," (mapped diagnostic context)."]}),"\n",(0,o.jsxs)(n.p,{children:["First, you need the ",(0,o.jsx)(n.code,{children:"armeria-logback"})," dependency:"]}),"\n",(0,o.jsx)(r,{boms:[{groupId:"com.linecorp.armeria",artifactId:"armeria-bom"}],dependencies:[{groupId:"com.linecorp.armeria",artifactId:"armeria-logback"}]}),"\n",(0,o.jsx)(n.p,{children:"Then, let's look at the following example:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-xml",children:'<?xml version="1.0" encoding="UTF-8"?>\n<configuration>\n  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">\n    <encoder>\n      <pattern>%d{HH:mm:ss.SSS} %X{remote.ip} %X{tls.cipher}\n               %X{req.headers.user-agent} %X{attrs.some_value} %msg%n</pattern>\n    </encoder>\n  </appender>\n\n  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">\n    <appender-ref ref="CONSOLE" />\n    <export>remote.ip</export>\n    <export>tls.cipher</export>\n    <export>req.headers.user-agent</export>\n    <export>attrs.some_value:com.example.AttrKeys#SOME_KEY</export>\n    \x3c!-- ... or alternatively:\n    <exports>remote.ip, remote.port, tls.cipher,\n             req.headers.user-agent,\n             attrs.some_value:com.example.AttrKeys#SOME_KEY</exports>\n    --\x3e\n    \x3c!-- ... or with wildcard:\n    <export>req.*</export>\n    --\x3e\n    \x3c!-- ... or with custom MDC key:\n    <export>remote_id=remote.id</export>\n    <export>UA=req.headers.user-agent</export>\n    <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>\n    --\x3e\n  </appender>\n  ...\n</configuration>\n'})}),"\n",(0,o.jsxs)(n.p,{children:["The above configuration defines an appender called ",(0,o.jsx)(n.code,{children:"RCEA"})," which exports the following:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"remote.ip"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"the IP address of the remote peer,"}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"tls.cipher"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"the SSL/TLS cipher suite of the connection,"}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"req.headers.user-agent"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"the user agent of the client,"}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.code,{children:"attrs.some_value"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["a custom attribute set via ",(0,o.jsx)(n.code,{children:'RequestContext.setAttr(AttributeKey.valueOf(AttrKeys.class, "SOME_KEY"), "SOME_VALUE")'})]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["... to the ",(0,o.jsx)(n.a,{href:"https://logback.qos.ch/manual/mdc.html",children:"MDC"})," property map and forwards the log message to the appender ",(0,o.jsx)(n.code,{children:"CONSOLE"}),", as defined in the\n",(0,o.jsx)(n.code,{children:"<appender-ref />"})," element."]}),"\n",(0,o.jsxs)(n.p,{children:["There are three types of properties you can export using ",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/logback/RequestContextExportingAppender.html",children:"RequestContextExportingAppender"}),"."]}),"\n",(0,o.jsx)(n.h2,{id:"built-in-properties",children:"Built-in properties"}),"\n",(0,o.jsxs)(n.p,{children:["A built-in property is a common property available for most requests. See the complete list of the built-in\nproperties and their MDC keys at ",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/logging/BuiltInProperty.html",children:"BuiltInProperty"}),".\nYou can also use wildcard character ",(0,o.jsx)(n.code,{children:"*"})," instead of listing all properties. For example:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.code,{children:'"*"'})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.code,{children:'"req.*"'})}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"http-request-and-response-headers",children:"HTTP request and response headers"}),"\n",(0,o.jsxs)(n.p,{children:["When the session protocol of the current connection is HTTP, a user can export HTTP headers of the current\nrequest and response. The MDC key of the exported header is ",(0,o.jsx)(n.code,{children:'"req.headers.<lower-case header name>"'})," or\n",(0,o.jsx)(n.code,{children:'"res.headers.<lower-case header name>"'}),". For example:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.code,{children:'"req.headers.user-agent"'})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.code,{children:'"res.headers.set-cookie"'})}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"custom-attributes",children:"Custom attributes"}),"\n",(0,o.jsxs)(n.p,{children:["A user can attach an arbitrary custom attribute to a ",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/RequestContext.html",children:"RequestContext"})," by using\n",(0,o.jsx)(n.a,{href:"/docs/advanced/logging/#custom-attributes",children:"RequestContext custom attributes"})," to store the information associated with the\nrequest being handled.\n",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/logback/RequestContextExportingAppender.html",children:"RequestContextExportingAppender"})," can export such attributes to the ",(0,o.jsx)(n.a,{href:"https://logback.qos.ch/manual/mdc.html",children:"MDC"})," property map as well."]}),"\n",(0,o.jsxs)(n.p,{children:["Unlike other property types, you need to specify the full name of an attribute as well as its alias.\nFor example, if you want to export an attribute ",(0,o.jsx)(n.code,{children:"com.example.Foo#ATTR_BAR"})," with the alias ",(0,o.jsx)(n.code,{children:"bar"}),", you need to add\n",(0,o.jsx)(n.code,{children:"<export>attrs.bar:com.example.Foo#ATTR_BAR</export>"})," to the XML configuration. The resulting MDC key to\naccess the attribute value is ",(0,o.jsx)(n.code,{children:"attrs.bar"}),", which follows the form of ",(0,o.jsx)(n.code,{children:"attrs.<alias>"}),"."]}),"\n",(0,o.jsx)(n.h3,{id:"using-an-alternative-string-converter-for-a-custom-attribute",children:"Using an alternative string converter for a custom attribute"}),"\n",(0,o.jsxs)(n.p,{children:["By default, ",(0,o.jsx)(n.a,{href:"type://https://javadoc.io/doc/com.linecorp.armeria/armeria-javadoc/latest/com/linecorp/armeria/common/logback/RequestContextExportingAppender.html",children:"RequestContextExportingAppender"})," uses ",(0,o.jsx)(n.code,{children:"Object.toString()"})," to convert an attribute value\ninto an ",(0,o.jsx)(n.a,{href:"https://logback.qos.ch/manual/mdc.html",children:"MDC"})," value. If you want an alternative string representation of an attribute value, you can define\na ",(0,o.jsx)(n.code,{children:"Function"})," class with a public no-args constructor that transforms an attribute value into a ",(0,o.jsx)(n.code,{children:"String"}),":"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-java",children:"package com.example;
1\n\npublic class SomeValue {\n    public final String value;\n\n    @Override\n    public String toString() {\n        // Too verbose for logging\n        return \"SomeValue(value=\" + value + ')';\n    }\n}\n\npublic class MyStringifier implements Function<SomeValue, String> {\n    @Override\n    public String apply(SomeValue o) {\n        return o.value;\n    }\n}\n"})}),"\n",(0,o.jsxs)(n.p,{children:["Once the ",(0,o.jsx)(n.code,{children:"Function"})," is implemented, specify the fully-qualified class name of the ",(0,o.jsx)(n.code,{children:"Function"})," implementation\nas the 3rd component of the ",(0,o.jsx)(n.code,{children:"<export />"})," element in the XML configuration:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-xml",children:'<?xml version="1.0" encoding="UTF-8"?>\n<configuration>\n  ...\n  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">\n    ...\n    <export>attrs.some_value:com.example.AttrKeys#SOME_KEY:com.example.MyStringifier</export>\n    ...\n  </appender>\n  ...\n</configuration>\n'})}),"\n",(0,o.jsx)(n.h2,{id:"customizing-mdc-keys",children:"Customizing MDC keys"}),"\n",(0,o.jsxs)(n.p,{children:["You can override the pre-defined MDC key by prepending an alias and an equals sign (=) to it.\nFor example, if you want to change ",(0,o.jsx)(n.code,{children:"req.id"})," to ",(0,o.jsx)(n.code,{children:"request_id"}),", use ",(0,o.jsx)(n.code,{children:"request_id=req.id"}),"."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-xml",children:'<?xml version="1.0" encoding="UTF-8"?>\n<configuration>\n  ...\n  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">\n    ...\n    <export>remote_id=remote.id</export>\n    <export>UA=req.headers.user-agent</export>\n    <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>\n    ...\n  </appender>\n  ...\n</configuration>\n'})}),"\n",(0,o.jsxs)(n.p,{children:["Note that a custom MDC key cannot be used with a wildcard expression ",(0,o.jsx)(n.code,{children:"*"})," or ",(0,o.jsx)(n.code,{children:"req.*"}),"."]}),"\n",(0,o.jsx)(n.h3,{id:"specifying-a-prefix-for-mdc-keys",children:"Specifying a prefix for MDC keys"}),"\n",(0,o.jsxs)(n.p,{children:["You can specify a prefix for MDC keys using the ",(0,o.jsx)(n.code,{children:"<prefix>"})," element.\nIf you want to add a prefix ",(0,o.jsx)(n.code,{children:"armeria."})," to all MDC keys,\nyou need to add ",(0,o.jsx)(n.code,{children:"<prefix>armeria.</prefix>"})," to the XML configuration."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-xml",children:'<?xml version="1.0" encoding="UTF-8"?>\n<configuration>\n  ...\n  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">\n    ...\n    \x3c!-- set the prefix of MDC keys --\x3e\n    <prefix>armeria.</prefix>\n    <export>remote.id</export>\n    <export>req.headers.user-agent</export>\n    <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>\n    ...\n  </appender>\n  ...\n</configuration>\n'})}),"\n",(0,o.jsxs)(n.p,{children:["When you want to specify a different prefix, you can define ",(0,o.jsx)(n.code,{children:"<prefix>"}),", ",(0,o.jsx)(n.code,{children:"<export>"}),", and ",(0,o.jsx)(n.code,{children:"<exports>"})," elements\nin the ",(0,o.jsx)(n.code,{children:"<exportGroup>"})," element."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-xml",children:'<?xml version="1.0" encoding="UTF-8"?>\n<configuration>\n  ...\n  <appender name="RCEA" class="com.linecorp.armeria.common.logback.RequestContextExportingAppender">\n    ...\n    \x3c!-- set the prefix of exports which is not wrapped with the <exportGroup> element --\x3e\n    <prefix>armeria.</prefix>\n    <export>remote.id</export>\n    <export>req.headers.user-agent</export>\n    ...\n    <exportGroup>\n      \x3c!-- set the prefix of exports in this <exportGroup> --\x3e\n      <prefix>some_prefix.</prefix>\n      <export>some_value=attr:com.example.AttrKeys#SOME_KEY</export>\n      ...\n    </exportGroup>\n    <exportGroup>\n      \x3c!-- if <prefix> is not defined, no prefix is added to exports --\x3e\n      <export>tracking_id=attr:com.example.AttrKeys#TRACKING_ID_KEY</export>\n      ...\n    </exportGroup>\n  </appender>\n  ...\n</configuration>\n'})})]})}function p(e={}){let{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},28453(e,n,r){r.d(n,{R:()=>i,x:()=>s});var t=r(96540);let o={},a=t.createContext(o);function i(e){let n=t.useContext(a);return t.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),t.createElement(a.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.