[{"data":1,"prerenderedAt":1827},["ShallowReactive",2],{"docs-page-\u002Fdocs\u002Fjava":3,"docs-surround-\u002Fdocs\u002Fjava":1818},{"id":4,"title":5,"body":6,"description":1811,"extension":1812,"meta":1813,"navigation":197,"path":1814,"seo":1815,"stem":1816,"__hash__":1817},"docs\u002Fdocs\u002F05.java.md","Java and Kotlin",{"type":7,"value":8,"toc":1800},"minimark",[9,13,39,44,47,91,94,167,173,177,343,346,431,442,455,459,465,491,498,632,639,794,797,936,943,988,1034,1038,1174,1177,1180,1183,1305,1332,1336,1538,1548,1552,1562,1579,1585,1589,1592,1773,1777,1796],[10,11,5],"h1",{"id":12},"java-and-kotlin",[14,15,16,20,21,26,27,30,31,34,35,38],"p",{},[17,18,19],"code",{},"com.infinitetoken:lumber"," is Lumber's client for Java, Kotlin and any other JVM language. It encrypts every log with ",[22,23,25],"a",{"href":24},"\u002Fdocs\u002Fencryption","AES-256-GCM"," before it leaves your process and has no dependencies beyond the JDK: it sends with ",[17,28,29],{},"java.net.http"," and encrypts with ",[17,32,33],{},"javax.crypto",". ",[17,36,37],{},"com.infinitetoken:lumber-logback"," plugs it into SLF4J and Logback, so Spring Boot and the libraries you already use can send to Lumber too.",[40,41,43],"h2",{"id":42},"install","Install",[14,45,46],{},"Both artifacts are on Maven Central. With Gradle:",[48,49,54],"pre",{"className":50,"code":51,"language":52,"meta":53,"style":53},"language-kotlin shiki shiki-themes github-dark","dependencies {\n    implementation(\"com.infinitetoken:lumber:1.0.0\")\n}\n","kotlin","",[17,55,56,69,85],{"__ignoreMap":53},[57,58,61,65],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"svObZ","dependencies",[57,66,68],{"class":67},"s95oV"," {\n",[57,70,72,75,78,82],{"class":59,"line":71},2,[57,73,74],{"class":63},"    implementation",[57,76,77],{"class":67},"(",[57,79,81],{"class":80},"sU2Wk","\"com.infinitetoken:lumber:1.0.0\"",[57,83,84],{"class":67},")\n",[57,86,88],{"class":59,"line":87},3,[57,89,90],{"class":67},"}\n",[14,92,93],{},"With Maven:",[48,95,99],{"className":96,"code":97,"language":98,"meta":53,"style":53},"language-xml shiki shiki-themes github-dark","\u003Cdependency>\n  \u003CgroupId>com.infinitetoken\u003C\u002FgroupId>\n  \u003CartifactId>lumber\u003C\u002FartifactId>\n  \u003Cversion>1.0.0\u003C\u002Fversion>\n\u003C\u002Fdependency>\n","xml",[17,100,101,113,128,142,157],{"__ignoreMap":53},[57,102,103,106,110],{"class":59,"line":60},[57,104,105],{"class":67},"\u003C",[57,107,109],{"class":108},"s4JwU","dependency",[57,111,112],{"class":67},">\n",[57,114,115,118,121,124,126],{"class":59,"line":71},[57,116,117],{"class":67},"  \u003C",[57,119,120],{"class":108},"groupId",[57,122,123],{"class":67},">com.infinitetoken\u003C\u002F",[57,125,120],{"class":108},[57,127,112],{"class":67},[57,129,130,132,135,138,140],{"class":59,"line":87},[57,131,117],{"class":67},[57,133,134],{"class":108},"artifactId",[57,136,137],{"class":67},">lumber\u003C\u002F",[57,139,134],{"class":108},[57,141,112],{"class":67},[57,143,145,147,150,153,155],{"class":59,"line":144},4,[57,146,117],{"class":67},[57,148,149],{"class":108},"version",[57,151,152],{"class":67},">1.0.0\u003C\u002F",[57,154,149],{"class":108},[57,156,112],{"class":67},[57,158,160,163,165],{"class":59,"line":159},5,[57,161,162],{"class":67},"\u003C\u002F",[57,164,109],{"class":108},[57,166,112],{"class":67},[14,168,169,170,172],{},"Java 11 or later. Not Android, which has no ",[17,171,29],{},".",[40,174,176],{"id":175},"quick-start","Quick start",[48,178,182],{"className":179,"code":180,"language":181,"meta":53,"style":53},"language-java shiki shiki-themes github-dark","import com.infinitetoken.lumber.LumberClient;\n\nLumberClient lumber = LumberClient.create(System.getenv(\"LUMBER_API_KEY\"));\n\nlumber.info(\"Server started\", Map.of(\"port\", 8080));\nlumber.warn(\"Rate limit approaching\", Map.of(\"requestId\", \"req-123\", \"userId\", 456));\nlumber.error(\"Payment failed\", Map.of(\"traceId\", \"trace-abc\", \"orderId\", 789), exception);\n","java",[17,183,184,193,199,227,231,264,303],{"__ignoreMap":53},[57,185,186,190],{"class":59,"line":60},[57,187,189],{"class":188},"snl16","import",[57,191,192],{"class":67}," com.infinitetoken.lumber.LumberClient;\n",[57,194,195],{"class":59,"line":71},[57,196,198],{"emptyLinePlaceholder":197},true,"\n",[57,200,201,204,207,210,213,216,219,221,224],{"class":59,"line":87},[57,202,203],{"class":67},"LumberClient lumber ",[57,205,206],{"class":188},"=",[57,208,209],{"class":67}," LumberClient.",[57,211,212],{"class":63},"create",[57,214,215],{"class":67},"(System.",[57,217,218],{"class":63},"getenv",[57,220,77],{"class":67},[57,222,223],{"class":80},"\"LUMBER_API_KEY\"",[57,225,226],{"class":67},"));\n",[57,228,229],{"class":59,"line":144},[57,230,198],{"emptyLinePlaceholder":197},[57,232,233,236,239,241,244,247,250,252,255,258,262],{"class":59,"line":159},[57,234,235],{"class":67},"lumber.",[57,237,238],{"class":63},"info",[57,240,77],{"class":67},[57,242,243],{"class":80},"\"Server started\"",[57,245,246],{"class":67},", Map.",[57,248,249],{"class":63},"of",[57,251,77],{"class":67},[57,253,254],{"class":80},"\"port\"",[57,256,257],{"class":67},", ",[57,259,261],{"class":260},"sDLfK","8080",[57,263,226],{"class":67},[57,265,267,269,272,274,277,279,281,283,286,288,291,293,296,298,301],{"class":59,"line":266},6,[57,268,235],{"class":67},[57,270,271],{"class":63},"warn",[57,273,77],{"class":67},[57,275,276],{"class":80},"\"Rate limit approaching\"",[57,278,246],{"class":67},[57,280,249],{"class":63},[57,282,77],{"class":67},[57,284,285],{"class":80},"\"requestId\"",[57,287,257],{"class":67},[57,289,290],{"class":80},"\"req-123\"",[57,292,257],{"class":67},[57,294,295],{"class":80},"\"userId\"",[57,297,257],{"class":67},[57,299,300],{"class":260},"456",[57,302,226],{"class":67},[57,304,306,308,311,313,316,318,320,322,325,327,330,332,335,337,340],{"class":59,"line":305},7,[57,307,235],{"class":67},[57,309,310],{"class":63},"error",[57,312,77],{"class":67},[57,314,315],{"class":80},"\"Payment failed\"",[57,317,246],{"class":67},[57,319,249],{"class":63},[57,321,77],{"class":67},[57,323,324],{"class":80},"\"traceId\"",[57,326,257],{"class":67},[57,328,329],{"class":80},"\"trace-abc\"",[57,331,257],{"class":67},[57,333,334],{"class":80},"\"orderId\"",[57,336,257],{"class":67},[57,338,339],{"class":260},"789",[57,341,342],{"class":67},"), exception);\n",[14,344,345],{},"In Kotlin:",[48,347,349],{"className":50,"code":348,"language":52,"meta":53,"style":53},"val lumber = LumberClient.create(System.getenv(\"LUMBER_API_KEY\"))\n\nlumber.info(\"Server started\", mapOf(\"port\" to 8080))\nlumber.error(\"Payment failed\", mapOf(\"orderId\" to 789), e)\n",[17,350,351,376,380,406],{"__ignoreMap":53},[57,352,353,356,359,361,363,365,367,369,371,373],{"class":59,"line":60},[57,354,355],{"class":188},"val",[57,357,358],{"class":67}," lumber ",[57,360,206],{"class":188},[57,362,209],{"class":67},[57,364,212],{"class":63},[57,366,215],{"class":67},[57,368,218],{"class":63},[57,370,77],{"class":67},[57,372,223],{"class":80},[57,374,375],{"class":67},"))\n",[57,377,378],{"class":59,"line":71},[57,379,198],{"emptyLinePlaceholder":197},[57,381,382,384,386,388,390,392,395,397,399,402,404],{"class":59,"line":87},[57,383,235],{"class":67},[57,385,238],{"class":63},[57,387,77],{"class":67},[57,389,243],{"class":80},[57,391,257],{"class":67},[57,393,394],{"class":63},"mapOf",[57,396,77],{"class":67},[57,398,254],{"class":80},[57,400,401],{"class":67}," to ",[57,403,261],{"class":260},[57,405,375],{"class":67},[57,407,408,410,412,414,416,418,420,422,424,426,428],{"class":59,"line":144},[57,409,235],{"class":67},[57,411,310],{"class":63},[57,413,77],{"class":67},[57,415,315],{"class":80},[57,417,257],{"class":67},[57,419,394],{"class":63},[57,421,77],{"class":67},[57,423,334],{"class":80},[57,425,401],{"class":67},[57,427,339],{"class":260},[57,429,430],{"class":67},"), e)\n",[14,432,433,434,437,438,441],{},"Make one client at startup and share it: it's safe to use from any thread. Logs are sent from a background thread, so logging never waits on the network, and logs still queued when the program exits get up to 5 seconds to send, from a shutdown hook. ",[17,435,436],{},"close()"," sends them and stops the client's thread, and ",[17,439,440],{},"flush(Duration)"," waits for them without stopping.",[14,443,444,445,449,450,454],{},"The API key comes from the Lumber app (see ",[22,446,448],{"href":447},"\u002Fdocs\u002Fgetting-started#create-an-app-in-lumber","Getting Started","). Keep it in an environment variable or your secrets manager, never in source control, and never in an app or game you ship to people (",[22,451,453],{"href":452},"\u002Fdocs\u002Fapi-keys#keep-the-key-on-your-servers","why",").",[40,456,458],{"id":457},"slf4j-logback-and-spring-boot","SLF4J, Logback and Spring Boot",[14,460,461,464],{},[17,462,463],{},"lumber-logback"," is a Logback appender, so the loggers your code and its libraries already use through SLF4J send to Lumber too. Spring Boot logs through Logback unless you've changed it.",[48,466,468],{"className":50,"code":467,"language":52,"meta":53,"style":53},"dependencies {\n    implementation(\"com.infinitetoken:lumber-logback:1.0.0\")\n}\n",[17,469,470,476,487],{"__ignoreMap":53},[57,471,472,474],{"class":59,"line":60},[57,473,64],{"class":63},[57,475,68],{"class":67},[57,477,478,480,482,485],{"class":59,"line":71},[57,479,74],{"class":63},[57,481,77],{"class":67},[57,483,484],{"class":80},"\"com.infinitetoken:lumber-logback:1.0.0\"",[57,486,84],{"class":67},[57,488,489],{"class":59,"line":87},[57,490,90],{"class":67},[14,492,493,494,497],{},"Add the appender to ",[17,495,496],{},"logback.xml",":",[48,499,501],{"className":96,"code":500,"language":98,"meta":53,"style":53},"\u003Cconfiguration>\n  \u003Cappender name=\"LUMBER\" class=\"com.infinitetoken.lumber.logback.LumberAppender\">\n    \u003CapiKey>${LUMBER_API_KEY}\u003C\u002FapiKey>\n    \u003Cenvironment>production\u003C\u002Fenvironment>\n  \u003C\u002Fappender>\n\n  \u003Croot level=\"INFO\">\n    \u003Cappender-ref ref=\"LUMBER\" \u002F>\n  \u003C\u002Froot>\n\u003C\u002Fconfiguration>\n",[17,502,503,512,537,552,566,575,579,596,614,623],{"__ignoreMap":53},[57,504,505,507,510],{"class":59,"line":60},[57,506,105],{"class":67},[57,508,509],{"class":108},"configuration",[57,511,112],{"class":67},[57,513,514,516,519,522,524,527,530,532,535],{"class":59,"line":71},[57,515,117],{"class":67},[57,517,518],{"class":108},"appender",[57,520,521],{"class":63}," name",[57,523,206],{"class":67},[57,525,526],{"class":80},"\"LUMBER\"",[57,528,529],{"class":63}," class",[57,531,206],{"class":67},[57,533,534],{"class":80},"\"com.infinitetoken.lumber.logback.LumberAppender\"",[57,536,112],{"class":67},[57,538,539,542,545,548,550],{"class":59,"line":87},[57,540,541],{"class":67},"    \u003C",[57,543,544],{"class":108},"apiKey",[57,546,547],{"class":67},">${LUMBER_API_KEY}\u003C\u002F",[57,549,544],{"class":108},[57,551,112],{"class":67},[57,553,554,556,559,562,564],{"class":59,"line":144},[57,555,541],{"class":67},[57,557,558],{"class":108},"environment",[57,560,561],{"class":67},">production\u003C\u002F",[57,563,558],{"class":108},[57,565,112],{"class":67},[57,567,568,571,573],{"class":59,"line":159},[57,569,570],{"class":67},"  \u003C\u002F",[57,572,518],{"class":108},[57,574,112],{"class":67},[57,576,577],{"class":59,"line":266},[57,578,198],{"emptyLinePlaceholder":197},[57,580,581,583,586,589,591,594],{"class":59,"line":305},[57,582,117],{"class":67},[57,584,585],{"class":108},"root",[57,587,588],{"class":63}," level",[57,590,206],{"class":67},[57,592,593],{"class":80},"\"INFO\"",[57,595,112],{"class":67},[57,597,599,601,604,607,609,611],{"class":59,"line":598},8,[57,600,541],{"class":67},[57,602,603],{"class":108},"appender-ref",[57,605,606],{"class":63}," ref",[57,608,206],{"class":67},[57,610,526],{"class":80},[57,612,613],{"class":67}," \u002F>\n",[57,615,617,619,621],{"class":59,"line":616},9,[57,618,570],{"class":67},[57,620,585],{"class":108},[57,622,112],{"class":67},[57,624,626,628,630],{"class":59,"line":625},10,[57,627,162],{"class":67},[57,629,509],{"class":108},[57,631,112],{"class":67},[14,633,634,635,638],{},"In Spring Boot, the same goes in ",[17,636,637],{},"logback-spring.xml",", beside Spring's console output:",[48,640,642],{"className":96,"code":641,"language":98,"meta":53,"style":53},"\u003Cconfiguration>\n  \u003Cinclude resource=\"org\u002Fspringframework\u002Fboot\u002Flogging\u002Flogback\u002Fdefaults.xml\" \u002F>\n  \u003Cinclude resource=\"org\u002Fspringframework\u002Fboot\u002Flogging\u002Flogback\u002Fconsole-appender.xml\" \u002F>\n\n  \u003Cappender name=\"LUMBER\" class=\"com.infinitetoken.lumber.logback.LumberAppender\">\n    \u003CapiKey>${LUMBER_API_KEY}\u003C\u002FapiKey>\n  \u003C\u002Fappender>\n\n  \u003Croot level=\"INFO\">\n    \u003Cappender-ref ref=\"CONSOLE\" \u002F>\n    \u003Cappender-ref ref=\"LUMBER\" \u002F>\n  \u003C\u002Froot>\n\u003C\u002Fconfiguration>\n",[17,643,644,652,669,684,688,708,720,728,732,746,761,776,785],{"__ignoreMap":53},[57,645,646,648,650],{"class":59,"line":60},[57,647,105],{"class":67},[57,649,509],{"class":108},[57,651,112],{"class":67},[57,653,654,656,659,662,664,667],{"class":59,"line":71},[57,655,117],{"class":67},[57,657,658],{"class":108},"include",[57,660,661],{"class":63}," resource",[57,663,206],{"class":67},[57,665,666],{"class":80},"\"org\u002Fspringframework\u002Fboot\u002Flogging\u002Flogback\u002Fdefaults.xml\"",[57,668,613],{"class":67},[57,670,671,673,675,677,679,682],{"class":59,"line":87},[57,672,117],{"class":67},[57,674,658],{"class":108},[57,676,661],{"class":63},[57,678,206],{"class":67},[57,680,681],{"class":80},"\"org\u002Fspringframework\u002Fboot\u002Flogging\u002Flogback\u002Fconsole-appender.xml\"",[57,683,613],{"class":67},[57,685,686],{"class":59,"line":144},[57,687,198],{"emptyLinePlaceholder":197},[57,689,690,692,694,696,698,700,702,704,706],{"class":59,"line":159},[57,691,117],{"class":67},[57,693,518],{"class":108},[57,695,521],{"class":63},[57,697,206],{"class":67},[57,699,526],{"class":80},[57,701,529],{"class":63},[57,703,206],{"class":67},[57,705,534],{"class":80},[57,707,112],{"class":67},[57,709,710,712,714,716,718],{"class":59,"line":266},[57,711,541],{"class":67},[57,713,544],{"class":108},[57,715,547],{"class":67},[57,717,544],{"class":108},[57,719,112],{"class":67},[57,721,722,724,726],{"class":59,"line":305},[57,723,570],{"class":67},[57,725,518],{"class":108},[57,727,112],{"class":67},[57,729,730],{"class":59,"line":598},[57,731,198],{"emptyLinePlaceholder":197},[57,733,734,736,738,740,742,744],{"class":59,"line":616},[57,735,117],{"class":67},[57,737,585],{"class":108},[57,739,588],{"class":63},[57,741,206],{"class":67},[57,743,593],{"class":80},[57,745,112],{"class":67},[57,747,748,750,752,754,756,759],{"class":59,"line":625},[57,749,541],{"class":67},[57,751,603],{"class":108},[57,753,606],{"class":63},[57,755,206],{"class":67},[57,757,758],{"class":80},"\"CONSOLE\"",[57,760,613],{"class":67},[57,762,764,766,768,770,772,774],{"class":59,"line":763},11,[57,765,541],{"class":67},[57,767,603],{"class":108},[57,769,606],{"class":63},[57,771,206],{"class":67},[57,773,526],{"class":80},[57,775,613],{"class":67},[57,777,779,781,783],{"class":59,"line":778},12,[57,780,570],{"class":67},[57,782,585],{"class":108},[57,784,112],{"class":67},[57,786,788,790,792],{"class":59,"line":787},13,[57,789,162],{"class":67},[57,791,509],{"class":108},[57,793,112],{"class":67},[14,795,796],{},"Then log as you already do:",[48,798,800],{"className":179,"code":799,"language":181,"meta":53,"style":53},"private static final Logger log = LoggerFactory.getLogger(Billing.class);\n\nlog.info(\"Report ready in {}s\", 2.4);\nlog.atWarn().addKeyValue(\"requestId\", \"req-123\").addKeyValue(\"rows\", 1500).log(\"Report slow\");\nlog.error(\"Payment failed\", exception);\nlog.error(MarkerFactory.getMarker(\"FATAL\"), \"Database unreachable\");\n",[17,801,802,827,831,851,898,911],{"__ignoreMap":53},[57,803,804,807,810,813,816,818,821,824],{"class":59,"line":60},[57,805,806],{"class":188},"private",[57,808,809],{"class":188}," static",[57,811,812],{"class":188}," final",[57,814,815],{"class":67}," Logger log ",[57,817,206],{"class":188},[57,819,820],{"class":67}," LoggerFactory.",[57,822,823],{"class":63},"getLogger",[57,825,826],{"class":67},"(Billing.class);\n",[57,828,829],{"class":59,"line":71},[57,830,198],{"emptyLinePlaceholder":197},[57,832,833,836,838,840,843,845,848],{"class":59,"line":87},[57,834,835],{"class":67},"log.",[57,837,238],{"class":63},[57,839,77],{"class":67},[57,841,842],{"class":80},"\"Report ready in {}s\"",[57,844,257],{"class":67},[57,846,847],{"class":260},"2.4",[57,849,850],{"class":67},");\n",[57,852,853,855,858,861,864,866,868,870,872,874,876,878,881,883,886,888,891,893,896],{"class":59,"line":144},[57,854,835],{"class":67},[57,856,857],{"class":63},"atWarn",[57,859,860],{"class":67},"().",[57,862,863],{"class":63},"addKeyValue",[57,865,77],{"class":67},[57,867,285],{"class":80},[57,869,257],{"class":67},[57,871,290],{"class":80},[57,873,454],{"class":67},[57,875,863],{"class":63},[57,877,77],{"class":67},[57,879,880],{"class":80},"\"rows\"",[57,882,257],{"class":67},[57,884,885],{"class":260},"1500",[57,887,454],{"class":67},[57,889,890],{"class":63},"log",[57,892,77],{"class":67},[57,894,895],{"class":80},"\"Report slow\"",[57,897,850],{"class":67},[57,899,900,902,904,906,908],{"class":59,"line":159},[57,901,835],{"class":67},[57,903,310],{"class":63},[57,905,77],{"class":67},[57,907,315],{"class":80},[57,909,910],{"class":67},", exception);\n",[57,912,913,915,917,920,923,925,928,931,934],{"class":59,"line":266},[57,914,835],{"class":67},[57,916,310],{"class":63},[57,918,919],{"class":67},"(MarkerFactory.",[57,921,922],{"class":63},"getMarker",[57,924,77],{"class":67},[57,926,927],{"class":80},"\"FATAL\"",[57,929,930],{"class":67},"), ",[57,932,933],{"class":80},"\"Database unreachable\"",[57,935,850],{"class":67},[14,937,938,939,942],{},"Each event becomes a log at the matching level: TRACE and DEBUG are DEBUG, and an event with a marker named ",[17,940,941],{},"FATAL"," is FATAL. Its message is the formatted message.",[944,945,946,972,981],"ul",{},[947,948,949,950,953,954,957,958,953,961,964,965,968,969,971],"li",{},"The MDC's entries and the event's key-value pairs go into the log's metadata, except ",[17,951,952],{},"requestId"," and ",[17,955,956],{},"traceId"," (or ",[17,959,960],{},"request_id",[17,962,963],{},"trace_id","), which are the log's own fields, and a ",[17,966,967],{},"context"," map, which goes into its context. So the ",[17,970,956],{}," Spring Boot's tracing puts in the MDC becomes the log's trace ID.",[947,973,974,975,953,978,172],{},"The logger and thread go into the context too, and an exception goes into the metadata as ",[17,976,977],{},"exception",[17,979,980],{},"stackTrace",[947,982,983,984,987],{},"The appender encrypts each event where it's logged and sends it from the client's thread, so it needs no ",[17,985,986],{},"AsyncAppender"," in front of it.",[14,989,990,991,257,993,257,995,257,998,257,1001,1004,1005,930,1008,1004,1011,1014,1015,1018,1019,1022,1023,1026,1027,1030,1031,172],{},"The appender takes the client's options as elements: ",[17,992,544],{},[17,994,558],{},[17,996,997],{},"endpoint",[17,999,1000],{},"autoContext",[17,1002,1003],{},"timeout"," (",[17,1006,1007],{},"10 seconds",[17,1009,1010],{},"shutdownTimeout",[17,1012,1013],{},"5 seconds",") and ",[17,1016,1017],{},"maxQueue",", plus ",[17,1020,1021],{},"includeCallerData"," (send the file, line and method that logged; off unless set, since Logback finds them from a stack trace on every event) and ",[17,1024,1025],{},"includeMdc"," (on unless set). Give it a ",[17,1028,1029],{},"ThresholdFilter"," to send fewer levels than your console shows. To share one client between the appender and your own calls, make the appender in code and pass it the client with ",[17,1032,1033],{},"setClient",[40,1035,1037],{"id":1036},"log-methods","Log methods",[1039,1040,1041,1054],"table",{},[1042,1043,1044],"thead",{},[1045,1046,1047,1051],"tr",{},[1048,1049,1050],"th",{},"Method",[1048,1052,1053],{},"Level",[1055,1056,1057,1071,1084,1103,1116,1128,1148,1162],"tbody",{},[1045,1058,1059,1068],{},[1060,1061,1062,257,1065],"td",{},[17,1063,1064],{},"debug(message)",[17,1066,1067],{},"debug(message, data)",[1060,1069,1070],{},"DEBUG",[1045,1072,1073,1081],{},[1060,1074,1075,257,1078],{},[17,1076,1077],{},"info(message)",[17,1079,1080],{},"info(message, data)",[1060,1082,1083],{},"INFO",[1045,1085,1086,1100],{},[1060,1087,1088,257,1091,257,1094,257,1097],{},[17,1089,1090],{},"warn(message)",[17,1092,1093],{},"warn(message, data)",[17,1095,1096],{},"warn(message, exception)",[17,1098,1099],{},"warn(message, data, exception)",[1060,1101,1102],{},"WARN",[1045,1104,1105,1113],{},[1060,1106,1107,1110,1111],{},[17,1108,1109],{},"error(...)",", with the same arguments as ",[17,1112,271],{},[1060,1114,1115],{},"ERROR",[1045,1117,1118,1125],{},[1060,1119,1120,1110,1123],{},[17,1121,1122],{},"fatal(...)",[17,1124,271],{},[1060,1126,1127],{},"FATAL. It doesn't exit",[1045,1129,1130,1135],{},[1060,1131,1132],{},[17,1133,1134],{},"log(level, message, data, exception)",[1060,1136,1137,1138,1141,1142,953,1145,1147],{},"Any ",[17,1139,1140],{},"LogLevel","; ",[17,1143,1144],{},"data",[17,1146,977],{}," can be null",[1045,1149,1150,1155],{},[1060,1151,1152],{},[17,1153,1154],{},"flush(timeout)",[1060,1156,1157,1158,1161],{},"Waits for queued logs to send; ",[17,1159,1160],{},"false"," if the time ran out first",[1045,1163,1164,1171],{},[1060,1165,1166,257,1168],{},[17,1167,436],{},[17,1169,1170],{},"close(timeout)",[1060,1172,1173],{},"Sends what's queued, waiting for the timeout at most, and stops",[14,1175,1176],{},"Every log method returns at once and never throws.",[40,1178,1179],{"id":1144},"Data",[14,1181,1182],{},"A map's keys become the log's fields:",[48,1184,1186],{"className":179,"code":1185,"language":181,"meta":53,"style":53},"lumber.info(\"User signed in\", Map.of(\n    \"requestId\", \"req-abc\",                   \u002F\u002F the log's request ID\n    \"traceId\", \"trace-xyz\",                   \u002F\u002F the log's trace ID\n    \"context\", Map.of(\"region\", \"us-east\"),   \u002F\u002F merged into its context\n    \"metadata\", Map.of(\"plan\", \"pro\"),        \u002F\u002F merged into its metadata\n    \"userId\", 123                             \u002F\u002F any other key goes into metadata\n));\n",[17,1187,1188,1206,1223,1238,1263,1288,1301],{"__ignoreMap":53},[57,1189,1190,1192,1194,1196,1199,1201,1203],{"class":59,"line":60},[57,1191,235],{"class":67},[57,1193,238],{"class":63},[57,1195,77],{"class":67},[57,1197,1198],{"class":80},"\"User signed in\"",[57,1200,246],{"class":67},[57,1202,249],{"class":63},[57,1204,1205],{"class":67},"(\n",[57,1207,1208,1211,1213,1216,1219],{"class":59,"line":71},[57,1209,1210],{"class":80},"    \"requestId\"",[57,1212,257],{"class":67},[57,1214,1215],{"class":80},"\"req-abc\"",[57,1217,1218],{"class":67},",                   ",[57,1220,1222],{"class":1221},"sAwPA","\u002F\u002F the log's request ID\n",[57,1224,1225,1228,1230,1233,1235],{"class":59,"line":87},[57,1226,1227],{"class":80},"    \"traceId\"",[57,1229,257],{"class":67},[57,1231,1232],{"class":80},"\"trace-xyz\"",[57,1234,1218],{"class":67},[57,1236,1237],{"class":1221},"\u002F\u002F the log's trace ID\n",[57,1239,1240,1243,1245,1247,1249,1252,1254,1257,1260],{"class":59,"line":144},[57,1241,1242],{"class":80},"    \"context\"",[57,1244,246],{"class":67},[57,1246,249],{"class":63},[57,1248,77],{"class":67},[57,1250,1251],{"class":80},"\"region\"",[57,1253,257],{"class":67},[57,1255,1256],{"class":80},"\"us-east\"",[57,1258,1259],{"class":67},"),   ",[57,1261,1262],{"class":1221},"\u002F\u002F merged into its context\n",[57,1264,1265,1268,1270,1272,1274,1277,1279,1282,1285],{"class":59,"line":159},[57,1266,1267],{"class":80},"    \"metadata\"",[57,1269,246],{"class":67},[57,1271,249],{"class":63},[57,1273,77],{"class":67},[57,1275,1276],{"class":80},"\"plan\"",[57,1278,257],{"class":67},[57,1280,1281],{"class":80},"\"pro\"",[57,1283,1284],{"class":67},"),        ",[57,1286,1287],{"class":1221},"\u002F\u002F merged into its metadata\n",[57,1289,1290,1293,1295,1298],{"class":59,"line":266},[57,1291,1292],{"class":80},"    \"userId\"",[57,1294,257],{"class":67},[57,1296,1297],{"class":260},"123",[57,1299,1300],{"class":1221},"                             \u002F\u002F any other key goes into metadata\n",[57,1302,1303],{"class":59,"line":305},[57,1304,226],{"class":67},[14,1306,1307,1308,1311,1312,1004,1314,1014,1317,1319,1320,1323,1324,1327,1328,1331],{},"An explicit ",[17,1309,1310],{},"metadata"," key wins over a colliding key. An exception goes into metadata as ",[17,1313,977],{},[17,1315,1316],{},"java.lang.IllegalStateException: card declined",[17,1318,980],{},". Values JSON has no type for are sent as text (an ",[17,1321,1322],{},"Instant"," or ",[17,1325,1326],{},"LocalDate"," as ISO 8601, a ",[17,1329,1330],{},"UUID"," as its text, an enum as its name), and a NaN as null. Everything, the message included, is encrypted.",[40,1333,1335],{"id":1334},"options","Options",[48,1337,1339],{"className":179,"code":1338,"language":181,"meta":53,"style":53},"LumberClient lumber = LumberClient.builder(System.getenv(\"LUMBER_API_KEY\"))\n    .environment(\"production\")              \u002F\u002F sent as `env` in every log's context\n    .autoContext(true)                      \u002F\u002F send the process id and hostname with every log\n    .caller(false)                          \u002F\u002F also send the file, line and method that logged\n    .callerDepth(0)                         \u002F\u002F frames to skip for `caller`, one per wrapper around Lumber\n    .timeout(Duration.ofSeconds(10))        \u002F\u002F how long to wait for Lumber to answer one log\n    .maxQueue(10_000)                       \u002F\u002F logs waiting to send before new ones are dropped\n    .shutdownTimeout(Duration.ofSeconds(5)) \u002F\u002F how long queued logs get when the program exits; zero adds no hook\n    .httpClient(httpClient)                 \u002F\u002F your own java.net.http.HttpClient, for a proxy or custom TLS\n    .warnings(message -> { \u002F* your logger *\u002F })\n    .build();\n",[17,1340,1341,1362,1380,1397,1414,1432,1455,1472,1493,1506,1528],{"__ignoreMap":53},[57,1342,1343,1345,1347,1349,1352,1354,1356,1358,1360],{"class":59,"line":60},[57,1344,203],{"class":67},[57,1346,206],{"class":188},[57,1348,209],{"class":67},[57,1350,1351],{"class":63},"builder",[57,1353,215],{"class":67},[57,1355,218],{"class":63},[57,1357,77],{"class":67},[57,1359,223],{"class":80},[57,1361,375],{"class":67},[57,1363,1364,1367,1369,1371,1374,1377],{"class":59,"line":71},[57,1365,1366],{"class":67},"    .",[57,1368,558],{"class":63},[57,1370,77],{"class":67},[57,1372,1373],{"class":80},"\"production\"",[57,1375,1376],{"class":67},")              ",[57,1378,1379],{"class":1221},"\u002F\u002F sent as `env` in every log's context\n",[57,1381,1382,1384,1386,1388,1391,1394],{"class":59,"line":87},[57,1383,1366],{"class":67},[57,1385,1000],{"class":63},[57,1387,77],{"class":67},[57,1389,1390],{"class":260},"true",[57,1392,1393],{"class":67},")                      ",[57,1395,1396],{"class":1221},"\u002F\u002F send the process id and hostname with every log\n",[57,1398,1399,1401,1404,1406,1408,1411],{"class":59,"line":144},[57,1400,1366],{"class":67},[57,1402,1403],{"class":63},"caller",[57,1405,77],{"class":67},[57,1407,1160],{"class":260},[57,1409,1410],{"class":67},")                          ",[57,1412,1413],{"class":1221},"\u002F\u002F also send the file, line and method that logged\n",[57,1415,1416,1418,1421,1423,1426,1429],{"class":59,"line":159},[57,1417,1366],{"class":67},[57,1419,1420],{"class":63},"callerDepth",[57,1422,77],{"class":67},[57,1424,1425],{"class":260},"0",[57,1427,1428],{"class":67},")                         ",[57,1430,1431],{"class":1221},"\u002F\u002F frames to skip for `caller`, one per wrapper around Lumber\n",[57,1433,1434,1436,1438,1441,1444,1446,1449,1452],{"class":59,"line":266},[57,1435,1366],{"class":67},[57,1437,1003],{"class":63},[57,1439,1440],{"class":67},"(Duration.",[57,1442,1443],{"class":63},"ofSeconds",[57,1445,77],{"class":67},[57,1447,1448],{"class":260},"10",[57,1450,1451],{"class":67},"))        ",[57,1453,1454],{"class":1221},"\u002F\u002F how long to wait for Lumber to answer one log\n",[57,1456,1457,1459,1461,1463,1466,1469],{"class":59,"line":305},[57,1458,1366],{"class":67},[57,1460,1017],{"class":63},[57,1462,77],{"class":67},[57,1464,1465],{"class":260},"10_000",[57,1467,1468],{"class":67},")                       ",[57,1470,1471],{"class":1221},"\u002F\u002F logs waiting to send before new ones are dropped\n",[57,1473,1474,1476,1478,1480,1482,1484,1487,1490],{"class":59,"line":598},[57,1475,1366],{"class":67},[57,1477,1010],{"class":63},[57,1479,1440],{"class":67},[57,1481,1443],{"class":63},[57,1483,77],{"class":67},[57,1485,1486],{"class":260},"5",[57,1488,1489],{"class":67},")) ",[57,1491,1492],{"class":1221},"\u002F\u002F how long queued logs get when the program exits; zero adds no hook\n",[57,1494,1495,1497,1500,1503],{"class":59,"line":616},[57,1496,1366],{"class":67},[57,1498,1499],{"class":63},"httpClient",[57,1501,1502],{"class":67},"(httpClient)                 ",[57,1504,1505],{"class":1221},"\u002F\u002F your own java.net.http.HttpClient, for a proxy or custom TLS\n",[57,1507,1508,1510,1513,1516,1519,1522,1525],{"class":59,"line":625},[57,1509,1366],{"class":67},[57,1511,1512],{"class":63},"warnings",[57,1514,1515],{"class":67},"(message ",[57,1517,1518],{"class":188},"->",[57,1520,1521],{"class":67}," { ",[57,1523,1524],{"class":1221},"\u002F* your logger *\u002F",[57,1526,1527],{"class":67}," })\n",[57,1529,1530,1532,1535],{"class":59,"line":763},[57,1531,1366],{"class":67},[57,1533,1534],{"class":63},"build",[57,1536,1537],{"class":67},"();\n",[14,1539,1540,1543,1544,1547],{},[17,1541,1542],{},"build()"," throws ",[17,1545,1546],{},"IllegalArgumentException"," when the API key is missing or has no valid encryption key.",[40,1549,1551],{"id":1550},"delivery","Delivery",[14,1553,1554,1555,1561],{},"Logs are sent in order, one ",[22,1556,1558],{"href":1557},"\u002Fdocs\u002Fhttp-api",[17,1559,1560],{},"POST \u002Flogs"," per log, from a daemon thread over connections kept open between logs.",[944,1563,1564,1567,1570,1573],{},[947,1565,1566],{},"A network error or a 5xx counts as a failed attempt. Each log gets 5 attempts, waiting 1, 2, 4, then 8 seconds between them, and is then dropped with a warning.",[947,1568,1569],{},"A log Lumber refuses (a 4xx, such as an invalid API key) is dropped at once with a warning, since sending it again won't help.",[947,1571,1572],{},"When your team has sent its plan's logs for the month, Lumber answers 429: the client warns once and drops logs until the month turns.",[947,1574,1575,1576,1578],{},"While Lumber can't be reached, up to ",[17,1577,1017],{}," logs wait; past that, new ones are dropped.",[14,1580,1581,1582,1584],{},"Warnings go to standard error unless you pass ",[17,1583,1512],{},", never through a logging library that could send them back to Lumber.",[40,1586,1588],{"id":1587},"serverless","Serverless",[14,1590,1591],{},"A serverless platform can freeze a function as soon as it returns, with logs still queued, and a frozen function's threads don't run. Make the client once, outside the handler, and flush it before returning, with a time limit so a slow answer can't hold the function to its maximum duration. On AWS Lambda:",[48,1593,1595],{"className":179,"code":1594,"language":181,"meta":53,"style":53},"public class Handler implements RequestHandler\u003CMap\u003CString, Object>, String> {\n    private static final LumberClient lumber = LumberClient.create(System.getenv(\"LUMBER_API_KEY\"));\n\n    @Override\n    public String handleRequest(Map\u003CString, Object> event, Context context) {\n        lumber.info(\"Handled request\", Map.of(\"requestId\", context.getAwsRequestId()));\n        lumber.flush(Duration.ofSeconds(10));\n        return \"ok\";\n    }\n}\n",[17,1596,1597,1632,1660,1664,1672,1707,1736,1753,1764,1769],{"__ignoreMap":53},[57,1598,1599,1602,1604,1607,1610,1613,1616,1619,1621,1624,1627,1629],{"class":59,"line":60},[57,1600,1601],{"class":188},"public",[57,1603,529],{"class":188},[57,1605,1606],{"class":63}," Handler",[57,1608,1609],{"class":188}," implements",[57,1611,1612],{"class":63}," RequestHandler",[57,1614,1615],{"class":67},"\u003CMap\u003C",[57,1617,1618],{"class":188},"String",[57,1620,257],{"class":67},[57,1622,1623],{"class":188},"Object",[57,1625,1626],{"class":67},">, ",[57,1628,1618],{"class":188},[57,1630,1631],{"class":67},"> {\n",[57,1633,1634,1637,1639,1641,1644,1646,1648,1650,1652,1654,1656,1658],{"class":59,"line":71},[57,1635,1636],{"class":188},"    private",[57,1638,809],{"class":188},[57,1640,812],{"class":188},[57,1642,1643],{"class":67}," LumberClient lumber ",[57,1645,206],{"class":188},[57,1647,209],{"class":67},[57,1649,212],{"class":63},[57,1651,215],{"class":67},[57,1653,218],{"class":63},[57,1655,77],{"class":67},[57,1657,223],{"class":80},[57,1659,226],{"class":67},[57,1661,1662],{"class":59,"line":87},[57,1663,198],{"emptyLinePlaceholder":197},[57,1665,1666,1669],{"class":59,"line":144},[57,1667,1668],{"class":67},"    @",[57,1670,1671],{"class":188},"Override\n",[57,1673,1674,1677,1680,1683,1686,1688,1690,1692,1695,1699,1702,1704],{"class":59,"line":159},[57,1675,1676],{"class":188},"    public",[57,1678,1679],{"class":67}," String ",[57,1681,1682],{"class":63},"handleRequest",[57,1684,1685],{"class":67},"(Map\u003C",[57,1687,1618],{"class":188},[57,1689,257],{"class":67},[57,1691,1623],{"class":188},[57,1693,1694],{"class":67},"> ",[57,1696,1698],{"class":1697},"s9osk","event",[57,1700,1701],{"class":67},", Context ",[57,1703,967],{"class":1697},[57,1705,1706],{"class":67},") {\n",[57,1708,1709,1712,1714,1716,1719,1721,1723,1725,1727,1730,1733],{"class":59,"line":266},[57,1710,1711],{"class":67},"        lumber.",[57,1713,238],{"class":63},[57,1715,77],{"class":67},[57,1717,1718],{"class":80},"\"Handled request\"",[57,1720,246],{"class":67},[57,1722,249],{"class":63},[57,1724,77],{"class":67},[57,1726,285],{"class":80},[57,1728,1729],{"class":67},", context.",[57,1731,1732],{"class":63},"getAwsRequestId",[57,1734,1735],{"class":67},"()));\n",[57,1737,1738,1740,1743,1745,1747,1749,1751],{"class":59,"line":305},[57,1739,1711],{"class":67},[57,1741,1742],{"class":63},"flush",[57,1744,1440],{"class":67},[57,1746,1443],{"class":63},[57,1748,77],{"class":67},[57,1750,1448],{"class":260},[57,1752,226],{"class":67},[57,1754,1755,1758,1761],{"class":59,"line":598},[57,1756,1757],{"class":188},"        return",[57,1759,1760],{"class":80}," \"ok\"",[57,1762,1763],{"class":67},";\n",[57,1765,1766],{"class":59,"line":616},[57,1767,1768],{"class":67},"    }\n",[57,1770,1771],{"class":59,"line":625},[57,1772,90],{"class":67},[40,1774,1776],{"id":1775},"source","Source",[14,1778,1779,1780,1786,1787,953,1791,1795],{},"The client is open source: ",[22,1781,1785],{"href":1782,"rel":1783},"https:\u002F\u002Fgithub.com\u002Finfinitetoken\u002FLumber-Java",[1784],"nofollow","Lumber-Java"," on GitHub, published as ",[22,1788,19],{"href":1789,"rel":1790},"https:\u002F\u002Fcentral.sonatype.com\u002Fartifact\u002Fcom.infinitetoken\u002Flumber",[1784],[22,1792,37],{"href":1793,"rel":1794},"https:\u002F\u002Fcentral.sonatype.com\u002Fartifact\u002Fcom.infinitetoken\u002Flumber-logback",[1784]," on Maven Central. It's tested against the same known answers for encryption and the wire format as Lumber's other clients.",[1797,1798,1799],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":53,"searchDepth":71,"depth":71,"links":1801},[1802,1803,1804,1805,1806,1807,1808,1809,1810],{"id":42,"depth":71,"text":43},{"id":175,"depth":71,"text":176},{"id":457,"depth":71,"text":458},{"id":1036,"depth":71,"text":1037},{"id":1144,"depth":71,"text":1179},{"id":1334,"depth":71,"text":1335},{"id":1550,"depth":71,"text":1551},{"id":1587,"depth":71,"text":1588},{"id":1775,"depth":71,"text":1776},"Encrypted logging for Java and Kotlin with com.infinitetoken:lumber: install it from Maven Central, send logs from SLF4J, Logback or Spring Boot, and every option.","md",{},"\u002Fdocs\u002Fjava",{"title":5,"description":1811},"docs\u002F05.java","O5YeGNIaNdAFUdBO_aVe9q0B2zo55HC0li5xAnRG7EU",[1819,1823],{"title":1820,"path":1821,"stem":1822,"children":-1},"Go","\u002Fdocs\u002Fgo","docs\u002F04.go",{"title":1824,"path":1825,"stem":1826,"children":-1},"C# and .NET","\u002Fdocs\u002Fdotnet","docs\u002F06.dotnet",1794332090951]