[{"data":1,"prerenderedAt":1792},["ShallowReactive",2],{"docs-page-\u002Fdocs\u002Fphp":3,"docs-surround-\u002Fdocs\u002Fphp":1783},{"id":4,"title":5,"body":6,"description":1776,"extension":1777,"meta":1778,"navigation":93,"path":1779,"seo":1780,"stem":1781,"__hash__":1782},"docs\u002Fdocs\u002F07.php.md","PHP",{"type":7,"value":8,"toc":1762},"minimark",[9,13,27,32,57,64,68,251,258,271,275,278,318,329,336,340,355,390,397,489,520,523,669,706,724,728,734,887,906,912,1002,1008,1054,1057,1061,1189,1192,1196,1199,1331,1369,1373,1578,1589,1593,1608,1611,1622,1625,1628,1655,1720,1726,1729,1739,1743,1758],[10,11,5],"h1",{"id":12},"php",[14,15,16,20,21,26],"p",{},[17,18,19],"code",{},"infinitetoken\u002Flumber"," is Lumber's PHP client. It encrypts every log with ",[22,23,25],"a",{"href":24},"\u002Fdocs\u002Fencryption","AES-256-GCM"," before it leaves your server. It's a PSR-3 logger, with a Laravel log channel and a Monolog handler (for Symfony and anything else on Monolog), and it never makes a visitor wait on the network.",[28,29,31],"h2",{"id":30},"install","Install",[33,34,39],"pre",{"className":35,"code":36,"language":37,"meta":38,"style":38},"language-bash shiki shiki-themes github-dark","composer require infinitetoken\u002Flumber\n","bash","",[17,40,41],{"__ignoreMap":38},[42,43,46,50,54],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"svObZ","composer",[42,51,53],{"class":52},"sU2Wk"," require",[42,55,56],{"class":52}," infinitetoken\u002Flumber\n",[14,58,59,60,63],{},"PHP 8.2 or later, with the curl and OpenSSL extensions. Its one other dependency is ",[17,61,62],{},"psr\u002Flog",".",[28,65,67],{"id":66},"quick-start","Quick start",[33,69,72],{"className":70,"code":71,"language":12,"meta":38,"style":38},"language-php shiki shiki-themes github-dark","use InfiniteToken\\Lumber\\Lumber;\n\n$lumber = new Lumber((string) getenv('LUMBER_API_KEY'));\n\n$lumber->info('Server started', ['port' => 8080]);\n$lumber->warning('Rate limit approaching', ['request_id' => 'req-123', 'user_id' => 456]);\n$lumber->error('Payment failed', ['trace_id' => 'trace-abc', 'order_id' => 789, 'exception' => $e]);\n",[17,73,74,88,95,131,136,168,206],{"__ignoreMap":38},[42,75,76,80,84],{"class":44,"line":45},[42,77,79],{"class":78},"snl16","use",[42,81,83],{"class":82},"sDLfK"," InfiniteToken\\Lumber\\Lumber",[42,85,87],{"class":86},"s95oV",";\n",[42,89,91],{"class":44,"line":90},2,[42,92,94],{"emptyLinePlaceholder":93},true,"\n",[42,96,98,101,104,107,110,113,116,119,122,125,128],{"class":44,"line":97},3,[42,99,100],{"class":86},"$lumber ",[42,102,103],{"class":78},"=",[42,105,106],{"class":78}," new",[42,108,109],{"class":82}," Lumber",[42,111,112],{"class":86},"((",[42,114,115],{"class":78},"string",[42,117,118],{"class":86},") ",[42,120,121],{"class":82},"getenv",[42,123,124],{"class":86},"(",[42,126,127],{"class":52},"'LUMBER_API_KEY'",[42,129,130],{"class":86},"));\n",[42,132,134],{"class":44,"line":133},4,[42,135,94],{"emptyLinePlaceholder":93},[42,137,139,142,145,148,150,153,156,159,162,165],{"class":44,"line":138},5,[42,140,141],{"class":86},"$lumber",[42,143,144],{"class":78},"->",[42,146,147],{"class":48},"info",[42,149,124],{"class":86},[42,151,152],{"class":52},"'Server started'",[42,154,155],{"class":86},", [",[42,157,158],{"class":52},"'port'",[42,160,161],{"class":78}," =>",[42,163,164],{"class":82}," 8080",[42,166,167],{"class":86},"]);\n",[42,169,171,173,175,178,180,183,185,188,190,193,196,199,201,204],{"class":44,"line":170},6,[42,172,141],{"class":86},[42,174,144],{"class":78},[42,176,177],{"class":48},"warning",[42,179,124],{"class":86},[42,181,182],{"class":52},"'Rate limit approaching'",[42,184,155],{"class":86},[42,186,187],{"class":52},"'request_id'",[42,189,161],{"class":78},[42,191,192],{"class":52}," 'req-123'",[42,194,195],{"class":86},", ",[42,197,198],{"class":52},"'user_id'",[42,200,161],{"class":78},[42,202,203],{"class":82}," 456",[42,205,167],{"class":86},[42,207,209,211,213,216,218,221,223,226,228,231,233,236,238,241,243,246,248],{"class":44,"line":208},7,[42,210,141],{"class":86},[42,212,144],{"class":78},[42,214,215],{"class":48},"error",[42,217,124],{"class":86},[42,219,220],{"class":52},"'Payment failed'",[42,222,155],{"class":86},[42,224,225],{"class":52},"'trace_id'",[42,227,161],{"class":78},[42,229,230],{"class":52}," 'trace-abc'",[42,232,195],{"class":86},[42,234,235],{"class":52},"'order_id'",[42,237,161],{"class":78},[42,239,240],{"class":82}," 789",[42,242,195],{"class":86},[42,244,245],{"class":52},"'exception'",[42,247,161],{"class":78},[42,249,250],{"class":86}," $e]);\n",[14,252,253,254,63],{},"That's all: the logs are sent when the request (or script) ends. In Laravel, skip this and set up the ",[22,255,257],{"href":256},"#laravel","log channel",[14,259,260,261,265,266,270],{},"The API key comes from the Lumber app (see ",[22,262,264],{"href":263},"\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,267,269],{"href":268},"\u002Fdocs\u002Fapi-keys#keep-the-key-on-your-servers","why",").",[28,272,274],{"id":273},"how-logs-are-sent","How logs are sent",[14,276,277],{},"Most PHP runs one request at a time, with nothing left running between requests, so the client can't send from a background thread the way Lumber's other clients do. Instead it holds each request's logs, already encrypted, and sends them as the request ends:",[279,280,281,301,312],"ul",{},[282,283,284,288,289,292,293,296,297,300],"li",{},[285,286,287],"strong",{},"PHP-FPM"," (nginx, Caddy, Apache with ",[17,290,291],{},"proxy_fcgi",", most hosting), ",[285,294,295],{},"LiteSpeed"," and ",[285,298,299],{},"FrankenPHP",": the response goes to the visitor first, and then the logs are sent, so nobody waits on Lumber. The PHP worker stays busy while they send, about a tenth of a second a log.",[282,302,303,296,306,311],{},[285,304,305],{},"Apache's mod_php",[285,307,308],{},[17,309,310],{},"php -S"," have no way to finish a response early, so the visitor's page finishes loading once the logs are sent.",[282,313,314,317],{},[285,315,316],{},"A command-line script"," sends what it holds as it exits.",[14,319,320,321,324,325,328],{},"Logs are sent one at a time in the order they were logged, over one connection kept open between them. Sending stops after 5 seconds (",[17,322,323],{},"flushTimeout","), and anything left is dropped with a warning; raise it if a request logs more than a few dozen lines. Up to 1,000 logs are held (",[17,326,327],{},"maxBuffer","); one more sends them there and then.",[14,330,331,332,63],{},"Long-running processes (queue workers, Octane, RoadRunner) never reach the end of a request, so they send at the end of each job or request instead. See ",[22,333,335],{"href":334},"#long-running-workers","Long-running workers",[28,337,339],{"id":338},"laravel","Laravel",[14,341,342,343,346,347,350,351,354],{},"Laravel finds the package by itself. Add your API key to ",[17,344,345],{},".env",", and ",[17,348,349],{},"lumber"," to the channels the ",[17,352,353],{},"stack"," channel writes to:",[33,356,358],{"className":35,"code":357,"language":37,"meta":38,"style":38},"LUMBER_API_KEY=your-api-key\nLOG_CHANNEL=stack\nLOG_STACK=single,lumber\n",[17,359,360,370,380],{"__ignoreMap":38},[42,361,362,365,367],{"class":44,"line":45},[42,363,364],{"class":86},"LUMBER_API_KEY",[42,366,103],{"class":78},[42,368,369],{"class":52},"your-api-key\n",[42,371,372,375,377],{"class":44,"line":90},[42,373,374],{"class":86},"LOG_CHANNEL",[42,376,103],{"class":78},[42,378,379],{"class":52},"stack\n",[42,381,382,385,387],{"class":44,"line":97},[42,383,384],{"class":86},"LOG_STACK",[42,386,103],{"class":78},[42,388,389],{"class":52},"single,lumber\n",[14,391,392,393,396],{},"Everything your app logs now goes to Lumber as well as ",[17,394,395],{},"storage\u002Flogs\u002Flaravel.log",":",[33,398,400],{"className":70,"code":399,"language":12,"meta":38,"style":38},"use Illuminate\\Support\\Facades\\Log;\n\nLog::info('Order placed', ['order_id' => 789, 'request_id' => $request->header('X-Request-Id')]);\nLog::channel('lumber')->warning('Sent to Lumber only');\n",[17,401,402,411,415,460],{"__ignoreMap":38},[42,403,404,406,409],{"class":44,"line":45},[42,405,79],{"class":78},[42,407,408],{"class":82}," Illuminate\\Support\\Facades\\Log",[42,410,87],{"class":86},[42,412,413],{"class":44,"line":90},[42,414,94],{"emptyLinePlaceholder":93},[42,416,417,420,423,425,427,430,432,434,436,438,440,442,444,447,449,452,454,457],{"class":44,"line":97},[42,418,419],{"class":82},"Log",[42,421,422],{"class":78},"::",[42,424,147],{"class":48},[42,426,124],{"class":86},[42,428,429],{"class":52},"'Order placed'",[42,431,155],{"class":86},[42,433,235],{"class":52},[42,435,161],{"class":78},[42,437,240],{"class":82},[42,439,195],{"class":86},[42,441,187],{"class":52},[42,443,161],{"class":78},[42,445,446],{"class":86}," $request",[42,448,144],{"class":78},[42,450,451],{"class":48},"header",[42,453,124],{"class":86},[42,455,456],{"class":52},"'X-Request-Id'",[42,458,459],{"class":86},")]);\n",[42,461,462,464,466,469,471,474,477,479,481,483,486],{"class":44,"line":133},[42,463,419],{"class":82},[42,465,422],{"class":78},[42,467,468],{"class":48},"channel",[42,470,124],{"class":86},[42,472,473],{"class":52},"'lumber'",[42,475,476],{"class":86},")",[42,478,144],{"class":78},[42,480,177],{"class":48},[42,482,124],{"class":86},[42,484,485],{"class":52},"'Sent to Lumber only'",[42,487,488],{"class":86},");\n",[279,490,491,494,500,517],{},[282,492,493],{},"Records are sent at the matching level: NOTICE is INFO, and CRITICAL, ALERT and EMERGENCY are FATAL.",[282,495,496,499],{},[17,497,498],{},"{placeholders}"," in messages are filled from the context, and an exception Laravel reports arrives with its trace.",[282,501,502,503,506,507,296,510,513,514,270],{},"What you add with Laravel's ",[17,504,505],{},"Context"," goes into each log's metadata, with ",[17,508,509],{},"trace_id",[17,511,512],{},"request_id"," as the log's own fields. Each log's context carries your app's environment (",[17,515,516],{},"APP_ENV",[282,518,519],{},"Logs are sent after the response has gone to the visitor, after each queued job, and after each of Octane's requests, tasks and ticks.",[14,521,522],{},"Settings, all optional but the key:",[524,525,526,542],"table",{},[527,528,529],"thead",{},[530,531,532,537,540],"tr",{},[533,534,535],"th",{},[17,536,345],{},[533,538,539],{},"Default",[533,541],{},[543,544,545,557,572,590,609,624,639,654],"tbody",{},[530,546,547,552,554],{},[548,549,550],"td",{},[17,551,364],{},[548,553],{},[548,555,556],{},"Your app's API key",[530,558,559,564,569],{},[548,560,561],{},[17,562,563],{},"LUMBER_LEVEL",[548,565,566],{},[17,567,568],{},"debug",[548,570,571],{},"The lowest level the channel sends",[530,573,574,579,583],{},[548,575,576],{},[17,577,578],{},"LUMBER_ENVIRONMENT",[548,580,581],{},[17,582,516],{},[548,584,585,586,589],{},"Sent as ",[17,587,588],{},"env"," in every log's context; empty sends none",[530,591,592,597,602],{},[548,593,594],{},[17,595,596],{},"LUMBER_SYNC",[548,598,599],{},[17,600,601],{},"false",[548,603,604,605,476],{},"Send each log as it's logged (see ",[22,606,608],{"href":607},"#serverless","Serverless",[530,610,611,616,621],{},[548,612,613],{},[17,614,615],{},"LUMBER_AUTO_CONTEXT",[548,617,618],{},[17,619,620],{},"true",[548,622,623],{},"Send the process id and hostname",[530,625,626,631,636],{},[548,627,628],{},[17,629,630],{},"LUMBER_TIMEOUT",[548,632,633],{},[17,634,635],{},"10",[548,637,638],{},"Seconds to wait for Lumber to answer one log",[530,640,641,646,651],{},[548,642,643],{},[17,644,645],{},"LUMBER_FLUSH_TIMEOUT",[548,647,648],{},[17,649,650],{},"5",[548,652,653],{},"Seconds sending may take as a request or job ends",[530,655,656,661,666],{},[548,657,658],{},[17,659,660],{},"LUMBER_MAX_BUFFER",[548,662,663],{},[17,664,665],{},"1000",[548,667,668],{},"Logs held before they're sent there and then",[14,670,671,674,675,678,679,681,682,685,686,689,690,195,693,195,696,296,699,702,703,63],{},[17,672,673],{},"php artisan vendor:publish --tag=lumber-config"," copies them to ",[17,676,677],{},"config\u002Flumber.php",". To change the channel itself, define ",[17,680,349],{}," in ",[17,683,684],{},"config\u002Flogging.php"," (",[17,687,688],{},"'driver' => 'custom', 'via' => InfiniteToken\\Lumber\\Laravel\\LumberChannel::class",", with Laravel's usual ",[17,691,692],{},"level",[17,694,695],{},"bubble",[17,697,698],{},"replace_placeholders",[17,700,701],{},"name","). The client is in the container as ",[17,704,705],{},"InfiniteToken\\Lumber\\Lumber",[14,707,708,709,711,712,714,715,717,718,720,721,63],{},"Laravel 12 or later. In an app whose ",[17,710,684],{}," predates ",[17,713,384],{},", add ",[17,716,473],{}," to the ",[17,719,353],{}," channel's ",[17,722,723],{},"channels",[28,725,727],{"id":726},"monolog-and-symfony","Monolog and Symfony",[14,729,730,733],{},[17,731,732],{},"LumberHandler"," sends Monolog's records (Monolog 3), so any code that logs through Monolog sends to Lumber:",[33,735,737],{"className":70,"code":736,"language":12,"meta":38,"style":38},"use InfiniteToken\\Lumber\\Lumber;\nuse InfiniteToken\\Lumber\\Monolog\\LumberHandler;\nuse Monolog\\Level;\nuse Monolog\\Logger;\n\n$lumber = new Lumber((string) getenv('LUMBER_API_KEY'));\n$logger = new Logger('app');\n$logger->pushHandler(new LumberHandler($lumber, Level::Info));\n\n$logger->error('Payment failed', ['order_id' => 789, 'exception' => $e]);\n",[17,738,739,747,756,765,774,778,802,821,853,858],{"__ignoreMap":38},[42,740,741,743,745],{"class":44,"line":45},[42,742,79],{"class":78},[42,744,83],{"class":82},[42,746,87],{"class":86},[42,748,749,751,754],{"class":44,"line":90},[42,750,79],{"class":78},[42,752,753],{"class":82}," InfiniteToken\\Lumber\\Monolog\\LumberHandler",[42,755,87],{"class":86},[42,757,758,760,763],{"class":44,"line":97},[42,759,79],{"class":78},[42,761,762],{"class":82}," Monolog\\Level",[42,764,87],{"class":86},[42,766,767,769,772],{"class":44,"line":133},[42,768,79],{"class":78},[42,770,771],{"class":82}," Monolog\\Logger",[42,773,87],{"class":86},[42,775,776],{"class":44,"line":138},[42,777,94],{"emptyLinePlaceholder":93},[42,779,780,782,784,786,788,790,792,794,796,798,800],{"class":44,"line":170},[42,781,100],{"class":86},[42,783,103],{"class":78},[42,785,106],{"class":78},[42,787,109],{"class":82},[42,789,112],{"class":86},[42,791,115],{"class":78},[42,793,118],{"class":86},[42,795,121],{"class":82},[42,797,124],{"class":86},[42,799,127],{"class":52},[42,801,130],{"class":86},[42,803,804,807,809,811,814,816,819],{"class":44,"line":208},[42,805,806],{"class":86},"$logger ",[42,808,103],{"class":78},[42,810,106],{"class":78},[42,812,813],{"class":82}," Logger",[42,815,124],{"class":86},[42,817,818],{"class":52},"'app'",[42,820,488],{"class":86},[42,822,824,827,829,832,834,837,840,843,846,848,851],{"class":44,"line":823},8,[42,825,826],{"class":86},"$logger",[42,828,144],{"class":78},[42,830,831],{"class":48},"pushHandler",[42,833,124],{"class":86},[42,835,836],{"class":78},"new",[42,838,839],{"class":82}," LumberHandler",[42,841,842],{"class":86},"($lumber, ",[42,844,845],{"class":82},"Level",[42,847,422],{"class":78},[42,849,850],{"class":82},"Info",[42,852,130],{"class":86},[42,854,856],{"class":44,"line":855},9,[42,857,94],{"emptyLinePlaceholder":93},[42,859,861,863,865,867,869,871,873,875,877,879,881,883,885],{"class":44,"line":860},10,[42,862,826],{"class":86},[42,864,144],{"class":78},[42,866,215],{"class":48},[42,868,124],{"class":86},[42,870,220],{"class":52},[42,872,155],{"class":86},[42,874,235],{"class":52},[42,876,161],{"class":78},[42,878,240],{"class":82},[42,880,195],{"class":86},[42,882,245],{"class":52},[42,884,161],{"class":78},[42,886,250],{"class":86},[14,888,889,890,894,895,898,899,901,902,905],{},"A record's context and extra (what processors add) are the log's data, as ",[22,891,893],{"href":892},"#data","below",", with the context winning, and its channel goes into the log's context. Add Monolog's ",[17,896,897],{},"PsrLogMessageProcessor"," to fill ",[17,900,498],{},". ",[17,903,904],{},"new LumberHandler($apiKey)"," makes its own client, if you'd rather not share one.",[14,907,908,909,396],{},"In Symfony, register the client and handler as services in ",[17,910,911],{},"config\u002Fservices.yaml",[33,913,917],{"className":914,"code":915,"language":916,"meta":38,"style":38},"language-yaml shiki shiki-themes github-dark","services:\n    # ...\n    InfiniteToken\\Lumber\\Lumber:\n        arguments:\n            $apiKey: '%env(LUMBER_API_KEY)%'\n            $environment: '%kernel.environment%'\n    InfiniteToken\\Lumber\\Monolog\\LumberHandler:\n        arguments:\n            $lumber: '@InfiniteToken\\Lumber\\Lumber'\n            $level: info\n","yaml",[17,918,919,928,934,941,948,959,969,976,982,992],{"__ignoreMap":38},[42,920,921,925],{"class":44,"line":45},[42,922,924],{"class":923},"s4JwU","services",[42,926,927],{"class":86},":\n",[42,929,930],{"class":44,"line":90},[42,931,933],{"class":932},"sAwPA","    # ...\n",[42,935,936,939],{"class":44,"line":97},[42,937,938],{"class":923},"    InfiniteToken\\Lumber\\Lumber",[42,940,927],{"class":86},[42,942,943,946],{"class":44,"line":133},[42,944,945],{"class":923},"        arguments",[42,947,927],{"class":86},[42,949,950,953,956],{"class":44,"line":138},[42,951,952],{"class":923},"            $apiKey",[42,954,955],{"class":86},": ",[42,957,958],{"class":52},"'%env(LUMBER_API_KEY)%'\n",[42,960,961,964,966],{"class":44,"line":170},[42,962,963],{"class":923},"            $environment",[42,965,955],{"class":86},[42,967,968],{"class":52},"'%kernel.environment%'\n",[42,970,971,974],{"class":44,"line":208},[42,972,973],{"class":923},"    InfiniteToken\\Lumber\\Monolog\\LumberHandler",[42,975,927],{"class":86},[42,977,978,980],{"class":44,"line":823},[42,979,945],{"class":923},[42,981,927],{"class":86},[42,983,984,987,989],{"class":44,"line":855},[42,985,986],{"class":923},"            $lumber",[42,988,955],{"class":86},[42,990,991],{"class":52},"'@InfiniteToken\\Lumber\\Lumber'\n",[42,993,994,997,999],{"class":44,"line":860},[42,995,996],{"class":923},"            $level",[42,998,955],{"class":86},[42,1000,1001],{"class":52},"info\n",[14,1003,1004,1005,396],{},"and give the handler to MonologBundle in ",[17,1006,1007],{},"config\u002Fpackages\u002Fmonolog.yaml",[33,1009,1011],{"className":914,"code":1010,"language":916,"meta":38,"style":38},"monolog:\n    handlers:\n        lumber:\n            type: service\n            id: InfiniteToken\\Lumber\\Monolog\\LumberHandler\n",[17,1012,1013,1020,1027,1034,1044],{"__ignoreMap":38},[42,1014,1015,1018],{"class":44,"line":45},[42,1016,1017],{"class":923},"monolog",[42,1019,927],{"class":86},[42,1021,1022,1025],{"class":44,"line":90},[42,1023,1024],{"class":923},"    handlers",[42,1026,927],{"class":86},[42,1028,1029,1032],{"class":44,"line":97},[42,1030,1031],{"class":923},"        lumber",[42,1033,927],{"class":86},[42,1035,1036,1039,1041],{"class":44,"line":133},[42,1037,1038],{"class":923},"            type",[42,1040,955],{"class":86},[42,1042,1043],{"class":52},"service\n",[42,1045,1046,1049,1051],{"class":44,"line":138},[42,1047,1048],{"class":923},"            id",[42,1050,955],{"class":86},[42,1052,1053],{"class":52},"InfiniteToken\\Lumber\\Monolog\\LumberHandler\n",[14,1055,1056],{},"Symfony resets its loggers between Messenger's messages, and the handler sends what's held when it's reset.",[28,1058,1060],{"id":1059},"log-methods","Log methods",[524,1062,1063,1072],{},[527,1064,1065],{},[530,1066,1067,1070],{},[533,1068,1069],{},"Method",[533,1071,845],{},[543,1073,1074,1084,1097,1110,1120,1139,1163,1179],{},[530,1075,1076,1081],{},[548,1077,1078],{},[17,1079,1080],{},"debug($message, $context)",[548,1082,1083],{},"DEBUG",[530,1085,1086,1094],{},[548,1087,1088,195,1091],{},[17,1089,1090],{},"info(...)",[17,1092,1093],{},"notice(...)",[548,1095,1096],{},"INFO",[530,1098,1099,1107],{},[548,1100,1101,195,1104],{},[17,1102,1103],{},"warning(...)",[17,1105,1106],{},"warn(...)",[548,1108,1109],{},"WARN",[530,1111,1112,1117],{},[548,1113,1114],{},[17,1115,1116],{},"error(...)",[548,1118,1119],{},"ERROR",[530,1121,1122,1136],{},[548,1123,1124,195,1127,195,1130,195,1133],{},[17,1125,1126],{},"critical(...)",[17,1128,1129],{},"alert(...)",[17,1131,1132],{},"emergency(...)",[17,1134,1135],{},"fatal(...)",[548,1137,1138],{},"FATAL. None of them exit",[530,1140,1141,1146],{},[548,1142,1143],{},[17,1144,1145],{},"log($level, $message, $context)",[548,1147,1148,1149,1152,1153,1155,1156,1159,1160],{},"A PSR-3 level (",[17,1150,1151],{},"Psr\\Log\\LogLevel","), a ",[17,1154,845],{},", or ",[17,1157,1158],{},"'warn'"," or ",[17,1161,1162],{},"'fatal'",[530,1164,1165,1170],{},[548,1166,1167],{},[17,1168,1169],{},"flush(?float $timeout)",[548,1171,1172,1173,1175,1176,1178],{},"Sends what's held now, waiting ",[17,1174,323],{}," seconds at most; ",[17,1177,601],{}," if time ran out first",[530,1180,1181,1186],{},[548,1182,1183],{},[17,1184,1185],{},"close(?float $timeout)",[548,1187,1188],{},"Sends what's held and stops; later logs are dropped",[14,1190,1191],{},"Logging never throws, except for an unknown level, which PSR-3 asks a logger to refuse.",[28,1193,1195],{"id":1194},"data","Data",[14,1197,1198],{},"The context array is the log's data:",[33,1200,1202],{"className":70,"code":1201,"language":12,"meta":38,"style":38},"$lumber->info('User signed in', [\n    'request_id' => 'req-abc',             \u002F\u002F the log's request ID\n    'trace_id' => 'trace-xyz',             \u002F\u002F the log's trace ID\n    'context' => ['region' => 'us-east'],  \u002F\u002F merged into its context\n    'metadata' => ['plan' => 'pro'],       \u002F\u002F merged into its metadata\n    'user_id' => 123,                      \u002F\u002F any other key goes into metadata\n    'exception' => $e,                     \u002F\u002F a Throwable: its class and message, and its trace\n]);\n",[17,1203,1204,1220,1236,1251,1275,1298,1314,1327],{"__ignoreMap":38},[42,1205,1206,1208,1210,1212,1214,1217],{"class":44,"line":45},[42,1207,141],{"class":86},[42,1209,144],{"class":78},[42,1211,147],{"class":48},[42,1213,124],{"class":86},[42,1215,1216],{"class":52},"'User signed in'",[42,1218,1219],{"class":86},", [\n",[42,1221,1222,1225,1227,1230,1233],{"class":44,"line":90},[42,1223,1224],{"class":52},"    'request_id'",[42,1226,161],{"class":78},[42,1228,1229],{"class":52}," 'req-abc'",[42,1231,1232],{"class":86},",             ",[42,1234,1235],{"class":932},"\u002F\u002F the log's request ID\n",[42,1237,1238,1241,1243,1246,1248],{"class":44,"line":97},[42,1239,1240],{"class":52},"    'trace_id'",[42,1242,161],{"class":78},[42,1244,1245],{"class":52}," 'trace-xyz'",[42,1247,1232],{"class":86},[42,1249,1250],{"class":932},"\u002F\u002F the log's trace ID\n",[42,1252,1253,1256,1258,1261,1264,1266,1269,1272],{"class":44,"line":133},[42,1254,1255],{"class":52},"    'context'",[42,1257,161],{"class":78},[42,1259,1260],{"class":86}," [",[42,1262,1263],{"class":52},"'region'",[42,1265,161],{"class":78},[42,1267,1268],{"class":52}," 'us-east'",[42,1270,1271],{"class":86},"],  ",[42,1273,1274],{"class":932},"\u002F\u002F merged into its context\n",[42,1276,1277,1280,1282,1284,1287,1289,1292,1295],{"class":44,"line":138},[42,1278,1279],{"class":52},"    'metadata'",[42,1281,161],{"class":78},[42,1283,1260],{"class":86},[42,1285,1286],{"class":52},"'plan'",[42,1288,161],{"class":78},[42,1290,1291],{"class":52}," 'pro'",[42,1293,1294],{"class":86},"],       ",[42,1296,1297],{"class":932},"\u002F\u002F merged into its metadata\n",[42,1299,1300,1303,1305,1308,1311],{"class":44,"line":170},[42,1301,1302],{"class":52},"    'user_id'",[42,1304,161],{"class":78},[42,1306,1307],{"class":82}," 123",[42,1309,1310],{"class":86},",                      ",[42,1312,1313],{"class":932},"\u002F\u002F any other key goes into metadata\n",[42,1315,1316,1319,1321,1324],{"class":44,"line":208},[42,1317,1318],{"class":52},"    'exception'",[42,1320,161],{"class":78},[42,1322,1323],{"class":86}," $e,                     ",[42,1325,1326],{"class":932},"\u002F\u002F a Throwable: its class and message, and its trace\n",[42,1328,1329],{"class":44,"line":823},[42,1330,167],{"class":86},[14,1332,1333,296,1336,1339,1340,1343,1344,1347,1348,1351,1352,685,1354,1357,1358,1361,1362,1364,1365,1368],{},[17,1334,1335],{},"requestId",[17,1337,1338],{},"traceId",", the API's own names, work too. An explicit ",[17,1341,1342],{},"metadata"," key wins over a colliding key. A ",[17,1345,1346],{},"Throwable"," under ",[17,1349,1350],{},"exception"," goes into metadata as ",[17,1353,1350],{},[17,1355,1356],{},"RuntimeException: Card declined",") and ",[17,1359,1360],{},"trace"," (PHP's own account of it, with the exceptions that led to it). ",[17,1363,498],{}," in the message are filled from the context, as PSR-3 describes, and the values are still sent. Values JSON has no form for are sent as text: a date as ISO 8601, an enum as its value, a ",[17,1366,1367],{},"JsonSerializable"," as what it serializes to, another object as its public properties, and a NaN as null. Everything, the message included, is encrypted.",[28,1370,1372],{"id":1371},"options","Options",[33,1374,1376],{"className":70,"code":1375,"language":12,"meta":38,"style":38},"$lumber = new Lumber(\n    (string) 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 function that logged\n    callerDepth: 0,            \u002F\u002F frames to skip for `caller`, one per function of yours wrapped around Lumber\n    sync: false,               \u002F\u002F send each log as it's logged, waiting for Lumber, rather than as the request ends\n    afterResponse: true,       \u002F\u002F finish the response before sending (PHP-FPM, LiteSpeed, FrankenPHP)\n    timeout: 10.0,             \u002F\u002F seconds to wait for Lumber to answer one log\n    flushTimeout: 5.0,         \u002F\u002F seconds sending may take at a flush, the end of the request included\n    maxBuffer: 1000,           \u002F\u002F logs held before a new one sends them there and then\n    warnings: fn (string $message) => $other->warning($message), \u002F\u002F in place of PHP's error log\n);\n",[17,1377,1378,1391,1409,1424,1439,1453,1469,1484,1499,1514,1529,1545,1573],{"__ignoreMap":38},[42,1379,1380,1382,1384,1386,1388],{"class":44,"line":45},[42,1381,100],{"class":86},[42,1383,103],{"class":78},[42,1385,106],{"class":78},[42,1387,109],{"class":82},[42,1389,1390],{"class":86},"(\n",[42,1392,1393,1396,1398,1400,1402,1404,1406],{"class":44,"line":90},[42,1394,1395],{"class":86},"    (",[42,1397,115],{"class":78},[42,1399,118],{"class":86},[42,1401,121],{"class":82},[42,1403,124],{"class":86},[42,1405,127],{"class":52},[42,1407,1408],{"class":86},"),\n",[42,1410,1411,1414,1416,1419,1421],{"class":44,"line":97},[42,1412,1413],{"class":48},"    environment",[42,1415,955],{"class":86},[42,1417,1418],{"class":52},"'production'",[42,1420,195],{"class":86},[42,1422,1423],{"class":932},"\u002F\u002F sent as `env` in every log's context\n",[42,1425,1426,1429,1431,1433,1436],{"class":44,"line":133},[42,1427,1428],{"class":48},"    autoContext",[42,1430,955],{"class":86},[42,1432,620],{"class":82},[42,1434,1435],{"class":86},",         ",[42,1437,1438],{"class":932},"\u002F\u002F send the process id and hostname with every log\n",[42,1440,1441,1444,1446,1448,1450],{"class":44,"line":138},[42,1442,1443],{"class":48},"    caller",[42,1445,955],{"class":86},[42,1447,601],{"class":82},[42,1449,1232],{"class":86},[42,1451,1452],{"class":932},"\u002F\u002F also send the file, line and function that logged\n",[42,1454,1455,1458,1460,1463,1466],{"class":44,"line":170},[42,1456,1457],{"class":48},"    callerDepth",[42,1459,955],{"class":86},[42,1461,1462],{"class":82},"0",[42,1464,1465],{"class":86},",            ",[42,1467,1468],{"class":932},"\u002F\u002F frames to skip for `caller`, one per function of yours wrapped around Lumber\n",[42,1470,1471,1474,1476,1478,1481],{"class":44,"line":208},[42,1472,1473],{"class":48},"    sync",[42,1475,955],{"class":86},[42,1477,601],{"class":82},[42,1479,1480],{"class":86},",               ",[42,1482,1483],{"class":932},"\u002F\u002F send each log as it's logged, waiting for Lumber, rather than as the request ends\n",[42,1485,1486,1489,1491,1493,1496],{"class":44,"line":823},[42,1487,1488],{"class":48},"    afterResponse",[42,1490,955],{"class":86},[42,1492,620],{"class":82},[42,1494,1495],{"class":86},",       ",[42,1497,1498],{"class":932},"\u002F\u002F finish the response before sending (PHP-FPM, LiteSpeed, FrankenPHP)\n",[42,1500,1501,1504,1506,1509,1511],{"class":44,"line":855},[42,1502,1503],{"class":48},"    timeout",[42,1505,955],{"class":86},[42,1507,1508],{"class":82},"10.0",[42,1510,1232],{"class":86},[42,1512,1513],{"class":932},"\u002F\u002F seconds to wait for Lumber to answer one log\n",[42,1515,1516,1519,1521,1524,1526],{"class":44,"line":860},[42,1517,1518],{"class":48},"    flushTimeout",[42,1520,955],{"class":86},[42,1522,1523],{"class":82},"5.0",[42,1525,1435],{"class":86},[42,1527,1528],{"class":932},"\u002F\u002F seconds sending may take at a flush, the end of the request included\n",[42,1530,1532,1535,1537,1539,1542],{"class":44,"line":1531},11,[42,1533,1534],{"class":48},"    maxBuffer",[42,1536,955],{"class":86},[42,1538,665],{"class":82},[42,1540,1541],{"class":86},",           ",[42,1543,1544],{"class":932},"\u002F\u002F logs held before a new one sends them there and then\n",[42,1546,1548,1551,1553,1556,1558,1560,1563,1565,1567,1570],{"class":44,"line":1547},12,[42,1549,1550],{"class":48},"    warnings",[42,1552,955],{"class":86},[42,1554,1555],{"class":78},"fn",[42,1557,685],{"class":86},[42,1559,115],{"class":78},[42,1561,1562],{"class":86}," $message) => $other",[42,1564,144],{"class":78},[42,1566,177],{"class":48},[42,1568,1569],{"class":86},"($message), ",[42,1571,1572],{"class":932},"\u002F\u002F in place of PHP's error log\n",[42,1574,1576],{"class":44,"line":1575},13,[42,1577,488],{"class":86},[14,1579,1580,1581,1584,1585,1588],{},"Finishing the response early writes a PHP session first (",[17,1582,1583],{},"session_write_close()","), or the visitor's next request would wait on the logs for the session's lock. Turn ",[17,1586,1587],{},"afterResponse"," off if something after your script's own end still needs to send output.",[28,1590,1592],{"id":1591},"delivery","Delivery",[279,1594,1595,1602,1605],{},[282,1596,1597,1598,1601],{},"A network error, a 5xx or no answer within ",[17,1599,1600],{},"timeout"," 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.",[282,1603,1604],{},"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.",[282,1606,1607],{},"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.",[14,1609,1610],{},"Every request starts afresh, so a request that finds Lumber unreachable, or at the monthly limit, leaves a note in the temp directory (named by a hash of your token), and the requests after it skip sending until it's over rather than each finding out for itself. While Lumber can't be reached, they hold off for 1 second, then 2, 4 and so on up to 30, until a log gets through. A Lumber outage costs your servers little, and never your visitors.",[14,1612,1613,1614,1617,1618,1621],{},"Warnings go to PHP's error log (",[17,1615,1616],{},"error_log()",") unless you pass ",[17,1619,1620],{},"warnings",", never through a logger, where they'd be sent back to Lumber.",[28,1623,335],{"id":1624},"long-running-workers",[14,1626,1627],{},"A process that serves many requests or jobs sends at the end of each:",[279,1629,1630,1639,1645],{},[282,1631,1632,1634,1635,1638],{},[285,1633,339],{},": queue workers (",[17,1636,1637],{},"queue:work",", Horizon) send after each job, and Octane after each request, task and tick, with nothing to set up.",[282,1640,1641,1644],{},[285,1642,1643],{},"Symfony",": Messenger's workers reset loggers between messages, and the Monolog handler sends then.",[282,1646,1647,1650,1651,1654],{},[285,1648,1649],{},"RoadRunner, Swoole, FrankenPHP's worker mode, or your own loop",": call ",[17,1652,1653],{},"flush()"," after each response or job:",[33,1656,1658],{"className":70,"code":1657,"language":12,"meta":38,"style":38},"while ($request = $worker->waitRequest()) {\n    $worker->respond(handle($request));\n    $lumber->flush(); \u002F\u002F after the response, so nobody waits on it\n}\n",[17,1659,1660,1681,1699,1715],{"__ignoreMap":38},[42,1661,1662,1665,1668,1670,1673,1675,1678],{"class":44,"line":45},[42,1663,1664],{"class":78},"while",[42,1666,1667],{"class":86}," ($request ",[42,1669,103],{"class":78},[42,1671,1672],{"class":86}," $worker",[42,1674,144],{"class":78},[42,1676,1677],{"class":48},"waitRequest",[42,1679,1680],{"class":86},"()) {\n",[42,1682,1683,1686,1688,1691,1693,1696],{"class":44,"line":90},[42,1684,1685],{"class":86},"    $worker",[42,1687,144],{"class":78},[42,1689,1690],{"class":48},"respond",[42,1692,124],{"class":86},[42,1694,1695],{"class":48},"handle",[42,1697,1698],{"class":86},"($request));\n",[42,1700,1701,1704,1706,1709,1712],{"class":44,"line":97},[42,1702,1703],{"class":86},"    $lumber",[42,1705,144],{"class":78},[42,1707,1708],{"class":48},"flush",[42,1710,1711],{"class":86},"(); ",[42,1713,1714],{"class":932},"\u002F\u002F after the response, so nobody waits on it\n",[42,1716,1717],{"class":44,"line":133},[42,1718,1719],{"class":86},"}\n",[14,1721,1722,1723,1725],{},"Without a ",[17,1724,1653],{},", logs still go once 1,000 are held or the process exits, which for a worker could be hours. A forked process (pcntl) starts with an empty buffer and its own connection; the parent sends what it held before the fork.",[28,1727,608],{"id":1728},"serverless",[14,1730,1731,1732,685,1735,1738],{},"AWS Lambda (Bref, Laravel Vapor) freezes the process as soon as the response is sent, and Google Cloud Run slows it to a crawl unless CPU is always allocated, so logs held for after the response may not go until the next request, or at all. Use ",[17,1733,1734],{},"sync: true",[17,1736,1737],{},"LUMBER_SYNC=true"," in Laravel): each log is sent as it's logged, and the request waits for Lumber to answer, about a tenth of a second a log.",[28,1740,1742],{"id":1741},"source","Source",[14,1744,1745,1746,1752,1753,1757],{},"The client is open source: ",[22,1747,1751],{"href":1748,"rel":1749},"https:\u002F\u002Fgithub.com\u002Finfinitetoken\u002FLumber-PHP",[1750],"nofollow","Lumber-PHP"," on GitHub, published as ",[22,1754,19],{"href":1755,"rel":1756},"https:\u002F\u002Fpackagist.org\u002Fpackages\u002Finfinitetoken\u002Flumber",[1750]," on Packagist. It's tested against the same known answers for encryption and the wire format as Lumber's other clients.",[1759,1760,1761],"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 .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":38,"searchDepth":90,"depth":90,"links":1763},[1764,1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775],{"id":30,"depth":90,"text":31},{"id":66,"depth":90,"text":67},{"id":273,"depth":90,"text":274},{"id":338,"depth":90,"text":339},{"id":726,"depth":90,"text":727},{"id":1059,"depth":90,"text":1060},{"id":1194,"depth":90,"text":1195},{"id":1371,"depth":90,"text":1372},{"id":1591,"depth":90,"text":1592},{"id":1624,"depth":90,"text":335},{"id":1728,"depth":90,"text":608},{"id":1741,"depth":90,"text":1742},"Encrypted logging for PHP with infinitetoken\u002Flumber: a PSR-3 logger with a Laravel log channel and a Monolog handler for Symfony, and how logs are sent after the response.","md",{},"\u002Fdocs\u002Fphp",{"title":5,"description":1776},"docs\u002F07.php","5yla85XTQ82wSk0WR0pdQ06uv3wK92Fz9248kJdE_JQ",[1784,1788],{"title":1785,"path":1786,"stem":1787,"children":-1},"C# and .NET","\u002Fdocs\u002Fdotnet","docs\u002F06.dotnet",{"title":1789,"path":1790,"stem":1791,"children":-1},"Command Line","\u002Fdocs\u002Fcli","docs\u002F08.cli",1794332090895]