[{"data":1,"prerenderedAt":943},["ShallowReactive",2],{"docs-page-\u002Fdocs\u002Fgo":3,"docs-surround-\u002Fdocs\u002Fgo":934},{"id":4,"title":5,"body":6,"description":927,"extension":928,"meta":929,"navigation":100,"path":930,"seo":931,"stem":932,"__hash__":933},"docs\u002Fdocs\u002F04.go.md","Go",{"type":7,"value":8,"toc":917},"minimark",[9,13,35,40,64,67,71,303,316,329,335,345,533,544,589,593,692,696,857,861,871,888,893,897,913],[10,11,5],"h1",{"id":12},"go",[14,15,16,20,21,26,27,30,31,34],"p",{},[17,18,19],"code",{},"github.com\u002Finfinitetoken\u002Flumber-go"," is Lumber's Go client. It encrypts every log with ",[22,23,25],"a",{"href":24},"\u002Fdocs\u002Fencryption","AES-256-GCM"," before it leaves your process, has no dependencies beyond the standard library, and plugs into ",[17,28,29],{},"log\u002Fslog",", so code that already logs with ",[17,32,33],{},"slog"," can send to Lumber.",[36,37,39],"h2",{"id":38},"install","Install",[41,42,47],"pre",{"className":43,"code":44,"language":45,"meta":46,"style":46},"language-bash shiki shiki-themes github-dark","go get github.com\u002Finfinitetoken\u002Flumber-go\n","bash","",[17,48,49],{"__ignoreMap":46},[50,51,54,57,61],"span",{"class":52,"line":53},"line",1,[50,55,12],{"class":56},"svObZ",[50,58,60],{"class":59},"sU2Wk"," get",[50,62,63],{"class":59}," github.com\u002Finfinitetoken\u002Flumber-go\n",[14,65,66],{},"Go 1.22 or later.",[36,68,70],{"id":69},"quick-start","Quick start",[41,72,75],{"className":73,"code":74,"language":12,"meta":46,"style":46},"language-go shiki shiki-themes github-dark","import lumber \"github.com\u002Finfinitetoken\u002Flumber-go\"\n\nclient, err := lumber.New(os.Getenv(\"LUMBER_API_KEY\"))\nif err != nil {\n    log.Fatal(err)\n}\ndefer client.Close(context.Background()) \u002F\u002F sends what's still queued\n\nclient.Info(\"Server started\", \"port\", 8080)\nclient.Warn(\"Rate limit approaching\", \"request_id\", \"req-123\", \"user_id\", 456)\nclient.Error(\"Payment failed\", \"trace_id\", \"trace-abc\", \"order_id\", 789, \"err\", err)\n",[17,76,77,95,102,132,151,163,169,194,199,227,262],{"__ignoreMap":46},[50,78,79,83,87,90,92],{"class":52,"line":53},[50,80,82],{"class":81},"snl16","import",[50,84,86],{"class":85},"s95oV"," lumber ",[50,88,89],{"class":59},"\"",[50,91,19],{"class":56},[50,93,94],{"class":59},"\"\n",[50,96,98],{"class":52,"line":97},2,[50,99,101],{"emptyLinePlaceholder":100},true,"\n",[50,103,105,108,111,114,117,120,123,126,129],{"class":52,"line":104},3,[50,106,107],{"class":85},"client, err ",[50,109,110],{"class":81},":=",[50,112,113],{"class":85}," lumber.",[50,115,116],{"class":56},"New",[50,118,119],{"class":85},"(os.",[50,121,122],{"class":56},"Getenv",[50,124,125],{"class":85},"(",[50,127,128],{"class":59},"\"LUMBER_API_KEY\"",[50,130,131],{"class":85},"))\n",[50,133,135,138,141,144,148],{"class":52,"line":134},4,[50,136,137],{"class":81},"if",[50,139,140],{"class":85}," err ",[50,142,143],{"class":81},"!=",[50,145,147],{"class":146},"sDLfK"," nil",[50,149,150],{"class":85}," {\n",[50,152,154,157,160],{"class":52,"line":153},5,[50,155,156],{"class":85},"    log.",[50,158,159],{"class":56},"Fatal",[50,161,162],{"class":85},"(err)\n",[50,164,166],{"class":52,"line":165},6,[50,167,168],{"class":85},"}\n",[50,170,172,175,178,181,184,187,190],{"class":52,"line":171},7,[50,173,174],{"class":81},"defer",[50,176,177],{"class":85}," client.",[50,179,180],{"class":56},"Close",[50,182,183],{"class":85},"(context.",[50,185,186],{"class":56},"Background",[50,188,189],{"class":85},"()) ",[50,191,193],{"class":192},"sAwPA","\u002F\u002F sends what's still queued\n",[50,195,197],{"class":52,"line":196},8,[50,198,101],{"emptyLinePlaceholder":100},[50,200,202,205,208,210,213,216,219,221,224],{"class":52,"line":201},9,[50,203,204],{"class":85},"client.",[50,206,207],{"class":56},"Info",[50,209,125],{"class":85},[50,211,212],{"class":59},"\"Server started\"",[50,214,215],{"class":85},", ",[50,217,218],{"class":59},"\"port\"",[50,220,215],{"class":85},[50,222,223],{"class":146},"8080",[50,225,226],{"class":85},")\n",[50,228,230,232,235,237,240,242,245,247,250,252,255,257,260],{"class":52,"line":229},10,[50,231,204],{"class":85},[50,233,234],{"class":56},"Warn",[50,236,125],{"class":85},[50,238,239],{"class":59},"\"Rate limit approaching\"",[50,241,215],{"class":85},[50,243,244],{"class":59},"\"request_id\"",[50,246,215],{"class":85},[50,248,249],{"class":59},"\"req-123\"",[50,251,215],{"class":85},[50,253,254],{"class":59},"\"user_id\"",[50,256,215],{"class":85},[50,258,259],{"class":146},"456",[50,261,226],{"class":85},[50,263,265,267,270,272,275,277,280,282,285,287,290,292,295,297,300],{"class":52,"line":264},11,[50,266,204],{"class":85},[50,268,269],{"class":56},"Error",[50,271,125],{"class":85},[50,273,274],{"class":59},"\"Payment failed\"",[50,276,215],{"class":85},[50,278,279],{"class":59},"\"trace_id\"",[50,281,215],{"class":85},[50,283,284],{"class":59},"\"trace-abc\"",[50,286,215],{"class":85},[50,288,289],{"class":59},"\"order_id\"",[50,291,215],{"class":85},[50,293,294],{"class":146},"789",[50,296,215],{"class":85},[50,298,299],{"class":59},"\"err\"",[50,301,302],{"class":85},", err)\n",[14,304,305,306,308,309,312,313,315],{},"The arguments after the message are ",[17,307,33],{},"'s: alternating keys and values, or ",[17,310,311],{},"slog.Attr",". Logs are sent from a background goroutine, so logging never waits on the network. Call ",[17,314,180],{}," before your program exits, or the logs still queued are lost; give its context a deadline if exiting must not wait long.",[14,317,318,319,323,324,328],{},"The API key comes from the Lumber app (see ",[22,320,322],{"href":321},"\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,325,327],{"href":326},"\u002Fdocs\u002Fapi-keys#keep-the-key-on-your-servers","why",").",[36,330,332,333],{"id":331},"with-logslog","With ",[17,334,29],{},[14,336,337,340,341,344],{},[17,338,339],{},"client.Handler"," is a ",[17,342,343],{},"slog.Handler",":",[41,346,348],{"className":73,"code":347,"language":12,"meta":46,"style":46},"slog.SetDefault(slog.New(client.Handler(&lumber.HandlerOptions{Level: slog.LevelInfo, AddSource: true})))\n\nslog.Info(\"Report ready\", \"request_id\", \"req-123\", \"rows\", 1500, \"took\", 2400*time.Millisecond)\n\nlogger := slog.Default().With(\"service\", \"billing\").WithGroup(\"job\")\nlogger.Error(\"Job failed\", \"id\", 42, \"err\", err)\n\nlogger.Log(ctx, lumber.LevelFatal, \"Database unreachable\")\n",[17,349,350,392,396,441,445,486,514,518],{"__ignoreMap":46},[50,351,352,355,358,361,363,366,369,371,374,377,380,383,386,389],{"class":52,"line":53},[50,353,354],{"class":85},"slog.",[50,356,357],{"class":56},"SetDefault",[50,359,360],{"class":85},"(slog.",[50,362,116],{"class":56},[50,364,365],{"class":85},"(client.",[50,367,368],{"class":56},"Handler",[50,370,125],{"class":85},[50,372,373],{"class":81},"&",[50,375,376],{"class":56},"lumber",[50,378,379],{"class":85},".",[50,381,382],{"class":56},"HandlerOptions",[50,384,385],{"class":85},"{Level: slog.LevelInfo, AddSource: ",[50,387,388],{"class":146},"true",[50,390,391],{"class":85},"})))\n",[50,393,394],{"class":52,"line":97},[50,395,101],{"emptyLinePlaceholder":100},[50,397,398,400,402,404,407,409,411,413,415,417,420,422,425,427,430,432,435,438],{"class":52,"line":104},[50,399,354],{"class":85},[50,401,207],{"class":56},[50,403,125],{"class":85},[50,405,406],{"class":59},"\"Report ready\"",[50,408,215],{"class":85},[50,410,244],{"class":59},[50,412,215],{"class":85},[50,414,249],{"class":59},[50,416,215],{"class":85},[50,418,419],{"class":59},"\"rows\"",[50,421,215],{"class":85},[50,423,424],{"class":146},"1500",[50,426,215],{"class":85},[50,428,429],{"class":59},"\"took\"",[50,431,215],{"class":85},[50,433,434],{"class":146},"2400",[50,436,437],{"class":81},"*",[50,439,440],{"class":85},"time.Millisecond)\n",[50,442,443],{"class":52,"line":134},[50,444,101],{"emptyLinePlaceholder":100},[50,446,447,450,452,455,458,461,464,466,469,471,474,476,479,481,484],{"class":52,"line":153},[50,448,449],{"class":85},"logger ",[50,451,110],{"class":81},[50,453,454],{"class":85}," slog.",[50,456,457],{"class":56},"Default",[50,459,460],{"class":85},"().",[50,462,463],{"class":56},"With",[50,465,125],{"class":85},[50,467,468],{"class":59},"\"service\"",[50,470,215],{"class":85},[50,472,473],{"class":59},"\"billing\"",[50,475,328],{"class":85},[50,477,478],{"class":56},"WithGroup",[50,480,125],{"class":85},[50,482,483],{"class":59},"\"job\"",[50,485,226],{"class":85},[50,487,488,491,493,495,498,500,503,505,508,510,512],{"class":52,"line":165},[50,489,490],{"class":85},"logger.",[50,492,269],{"class":56},[50,494,125],{"class":85},[50,496,497],{"class":59},"\"Job failed\"",[50,499,215],{"class":85},[50,501,502],{"class":59},"\"id\"",[50,504,215],{"class":85},[50,506,507],{"class":146},"42",[50,509,215],{"class":85},[50,511,299],{"class":59},[50,513,302],{"class":85},[50,515,516],{"class":52,"line":171},[50,517,101],{"emptyLinePlaceholder":100},[50,519,520,522,525,528,531],{"class":52,"line":196},[50,521,490],{"class":85},[50,523,524],{"class":56},"Log",[50,526,527],{"class":85},"(ctx, lumber.LevelFatal, ",[50,529,530],{"class":59},"\"Database unreachable\"",[50,532,226],{"class":85},[14,534,535,536,539,540,543],{},"Each record becomes a log at the matching level: from ",[17,537,538],{},"slog.LevelError"," up is ERROR, and from ",[17,541,542],{},"lumber.LevelFatal"," (12) up is FATAL.",[545,546,547,551,573,576],"ul",{},[548,549,550],"li",{},"Attributes go into the log's metadata, with groups as nested objects.",[548,552,553,554,557,558,561,562,557,565,568,569,572],{},"Three are special at the top level: ",[17,555,556],{},"request_id"," and ",[17,559,560],{},"trace_id"," (or ",[17,563,564],{},"requestId",[17,566,567],{},"traceId",") are the log's own fields, and a ",[17,570,571],{},"context"," group or map goes into its context.",[548,574,575],{},"An error is sent as its message.",[548,577,578,581,582,585,586,379],{},[17,579,580],{},"AddSource"," sends the file, line and function that logged in the context. ",[17,583,584],{},"Level"," defaults to ",[17,587,588],{},"slog.LevelInfo",[36,590,592],{"id":591},"log-methods","Log methods",[594,595,596,608],"table",{},[597,598,599],"thead",{},[600,601,602,606],"tr",{},[603,604,605],"th",{},"Method",[603,607,584],{},[609,610,611,622,632,642,652,666,679],"tbody",{},[600,612,613,619],{},[614,615,616],"td",{},[17,617,618],{},"Debug(msg, args...)",[614,620,621],{},"DEBUG",[600,623,624,629],{},[614,625,626],{},[17,627,628],{},"Info(msg, args...)",[614,630,631],{},"INFO",[600,633,634,639],{},[614,635,636],{},[17,637,638],{},"Warn(msg, args...)",[614,640,641],{},"WARN",[600,643,644,649],{},[614,645,646],{},[17,647,648],{},"Error(msg, args...)",[614,650,651],{},"ERROR",[600,653,654,659],{},[614,655,656],{},[17,657,658],{},"Fatal(msg, args...)",[614,660,661,662,665],{},"FATAL. Unlike ",[17,663,664],{},"log.Fatal",", it doesn't exit",[600,667,668,673],{},[614,669,670],{},[17,671,672],{},"Flush(ctx)",[614,674,675,676],{},"Waits for queued logs to send, or for ",[17,677,678],{},"ctx",[600,680,681,686],{},[614,682,683],{},[17,684,685],{},"Close(ctx)",[614,687,688,689,691],{},"Sends what's queued, waiting for ",[17,690,678],{}," at most, and stops",[36,693,695],{"id":694},"options","Options",[41,697,699],{"className":73,"code":698,"language":12,"meta":46,"style":46},"client, err := lumber.New(os.Getenv(\"LUMBER_API_KEY\"),\n    lumber.WithEnvironment(\"production\"), \u002F\u002F sent as `env` in every log's context\n    lumber.WithCaller(0),                 \u002F\u002F send the file, line and function of the client's own calls; 1 skips a wrapper\n    lumber.WithoutAutoContext(),          \u002F\u002F don't send the process id and hostname\n    lumber.WithTimeout(10*time.Second),   \u002F\u002F how long to wait for Lumber to answer one log\n    lumber.WithMaxQueue(10_000),          \u002F\u002F logs waiting to send before new ones are dropped\n    lumber.WithHTTPClient(httpClient),    \u002F\u002F your own http.Client, for a proxy or custom TLS\n    lumber.WithWarnings(func(msg string) { \u002F* your logger *\u002F }),\n)\n",[17,700,701,722,741,759,772,792,810,823,853],{"__ignoreMap":46},[50,702,703,705,707,709,711,713,715,717,719],{"class":52,"line":53},[50,704,107],{"class":85},[50,706,110],{"class":81},[50,708,113],{"class":85},[50,710,116],{"class":56},[50,712,119],{"class":85},[50,714,122],{"class":56},[50,716,125],{"class":85},[50,718,128],{"class":59},[50,720,721],{"class":85},"),\n",[50,723,724,727,730,732,735,738],{"class":52,"line":97},[50,725,726],{"class":85},"    lumber.",[50,728,729],{"class":56},"WithEnvironment",[50,731,125],{"class":85},[50,733,734],{"class":59},"\"production\"",[50,736,737],{"class":85},"), ",[50,739,740],{"class":192},"\u002F\u002F sent as `env` in every log's context\n",[50,742,743,745,748,750,753,756],{"class":52,"line":104},[50,744,726],{"class":85},[50,746,747],{"class":56},"WithCaller",[50,749,125],{"class":85},[50,751,752],{"class":146},"0",[50,754,755],{"class":85},"),                 ",[50,757,758],{"class":192},"\u002F\u002F send the file, line and function of the client's own calls; 1 skips a wrapper\n",[50,760,761,763,766,769],{"class":52,"line":134},[50,762,726],{"class":85},[50,764,765],{"class":56},"WithoutAutoContext",[50,767,768],{"class":85},"(),          ",[50,770,771],{"class":192},"\u002F\u002F don't send the process id and hostname\n",[50,773,774,776,779,781,784,786,789],{"class":52,"line":153},[50,775,726],{"class":85},[50,777,778],{"class":56},"WithTimeout",[50,780,125],{"class":85},[50,782,783],{"class":146},"10",[50,785,437],{"class":81},[50,787,788],{"class":85},"time.Second),   ",[50,790,791],{"class":192},"\u002F\u002F how long to wait for Lumber to answer one log\n",[50,793,794,796,799,801,804,807],{"class":52,"line":165},[50,795,726],{"class":85},[50,797,798],{"class":56},"WithMaxQueue",[50,800,125],{"class":85},[50,802,803],{"class":146},"10_000",[50,805,806],{"class":85},"),          ",[50,808,809],{"class":192},"\u002F\u002F logs waiting to send before new ones are dropped\n",[50,811,812,814,817,820],{"class":52,"line":171},[50,813,726],{"class":85},[50,815,816],{"class":56},"WithHTTPClient",[50,818,819],{"class":85},"(httpClient),    ",[50,821,822],{"class":192},"\u002F\u002F your own http.Client, for a proxy or custom TLS\n",[50,824,825,827,830,832,835,837,841,844,847,850],{"class":52,"line":196},[50,826,726],{"class":85},[50,828,829],{"class":56},"WithWarnings",[50,831,125],{"class":85},[50,833,834],{"class":81},"func",[50,836,125],{"class":85},[50,838,840],{"class":839},"s9osk","msg",[50,842,843],{"class":81}," string",[50,845,846],{"class":85},") { ",[50,848,849],{"class":192},"\u002F* your logger *\u002F",[50,851,852],{"class":85}," }),\n",[50,854,855],{"class":52,"line":201},[50,856,226],{"class":85},[36,858,860],{"id":859},"delivery","Delivery",[14,862,863,864,870],{},"Logs are sent in order, one ",[22,865,867],{"href":866},"\u002Fdocs\u002Fhttp-api",[17,868,869],{},"POST \u002Flogs"," per log, over a connection kept open between logs.",[545,872,873,876,879,882],{},[548,874,875],{},"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.",[548,877,878],{},"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.",[548,880,881],{},"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.",[548,883,884,885,887],{},"While Lumber can't be reached, up to ",[17,886,798],{}," logs wait; past that, new ones are dropped.",[14,889,890,891,379],{},"Warnings go to standard error unless you pass ",[17,892,829],{},[36,894,896],{"id":895},"source","Source",[14,898,899,900,906,907,912],{},"The client is open source: ",[22,901,905],{"href":902,"rel":903},"https:\u002F\u002Fgithub.com\u002Finfinitetoken\u002Flumber-go",[904],"nofollow","lumber-go"," on GitHub, with its reference on ",[22,908,911],{"href":909,"rel":910},"https:\u002F\u002Fpkg.go.dev\u002Fgithub.com\u002Finfinitetoken\u002Flumber-go",[904],"pkg.go.dev",". It's tested against the same known answers for encryption and the wire format as Lumber's other clients.",[914,915,916],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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 .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}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":46,"searchDepth":97,"depth":97,"links":918},[919,920,921,923,924,925,926],{"id":38,"depth":97,"text":39},{"id":69,"depth":97,"text":70},{"id":331,"depth":97,"text":922},"With log\u002Fslog",{"id":591,"depth":97,"text":592},{"id":694,"depth":97,"text":695},{"id":859,"depth":97,"text":860},{"id":895,"depth":97,"text":896},"Encrypted logging for Go with lumber-go: install it, send logs from log\u002Fslog, and every option.","md",{},"\u002Fdocs\u002Fgo",{"title":5,"description":927},"docs\u002F04.go","pEmNeOer5gvErEXR6zOgj6GIaBEH8btPwzbF5AYzK_I",[935,939],{"title":936,"path":937,"stem":938,"children":-1},"Python","\u002Fdocs\u002Fpython","docs\u002F03.python",{"title":940,"path":941,"stem":942,"children":-1},"Java and Kotlin","\u002Fdocs\u002Fjava","docs\u002F05.java",1794332090948]