[{"data":1,"prerenderedAt":1383},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Flog-messages-to-qgis-message-log-pyqgis":3},{"id":4,"title":5,"body":6,"description":1372,"extension":1373,"meta":1374,"navigation":235,"path":1379,"seo":1380,"stem":1381,"__hash__":1382},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Flog-messages-to-qgis-message-log-pyqgis\u002Findex.md","Log Messages to the QGIS Message Log in PyQGIS",{"type":7,"value":8,"toc":1358},"minimark",[9,13,21,34,169,174,199,203,286,314,317,321,392,397,401,404,489,505,596,600,603,695,716,720,726,815,841,845,848,854,1027,1040,1046,1122,1126,1132,1205,1209,1266,1270,1283,1287,1293,1299,1305,1315,1321,1325,1354],[10,11,5],"h1",{"id":12},"log-messages-to-the-qgis-message-log-in-pyqgis",[14,15,16,20],"p",{},[17,18,19],"code",{},"print()"," goes to the Python console, which is fine while you are looking at it and useless everywhere else — inside a plugin a user is running, inside a Processing algorithm, inside a background task, on a server. QGIS has a proper logging surface: a message log with tags and severity levels, and a message bar for the things a user needs to see right now. Knowing which of the three to reach for is most of the skill.",[14,22,23,24,29,30,33],{},"This recipe belongs to ",[25,26,28],"a",{"href":27},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002F","Debugging PyQGIS Scripts",". It covers writing to the message log, choosing tags and levels, using the message bar for user-facing notices, reading what QGIS itself logs, and forwarding everything into Python's ",[17,31,32],{},"logging"," for unattended runs.",[14,35,36],{},[37,38,43,47,51,58,75,84,94,99,104,108,112,118,121,124,127,130,135,139,142,145,148,153,158,163],"svg",{"viewBox":39,"role":40,"ariaLabel":41,"xmlns":42},"0 0 760 276","img","Three output surfaces compared: the Python console for interactive work, the message log panel for diagnostics, and the message bar for user-facing notices","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[44,45,46],"title",{},"Three places a message can go, for three different readers",[48,49,50],"desc",{},"The Python console shows print output and suits interactive experimentation only. The message log panel keeps tagged, levelled, timestamped entries and is where diagnostics belong. The message bar shows a transient banner over the canvas and is for messages the user must act on. A fourth path forwards message log entries into Python logging for headless runs.",[52,53],"rect",{"x":54,"y":54,"width":55,"height":56,"fill":57},"0","760","276","#f6f3ea",[59,60,61],"defs",{},[62,63,70],"marker",{"id":64,"viewBox":65,"refX":66,"refY":67,"markerWidth":68,"markerHeight":68,"orient":69},"msgArrow","0 0 10 10","8","5","7","auto-start-reverse",[71,72],"path",{"d":73,"fill":74},"M0 0 L10 5 L0 10 z","#2f3b35",[76,77,83],"text",{"x":78,"y":79,"style":80,"fill":81,"textAnchor":82},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Who is going to read this message?",[52,85],{"x":86,"y":87,"width":88,"height":89,"rx":90,"fill":91,"stroke":92,"style":93},"16","52","232","130","10","#fdf2e2","#b45309","stroke-width:2",[76,95,19],{"x":96,"y":97,"style":98,"fill":92,"textAnchor":82},"132","78","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif",[76,100,103],{"x":96,"y":101,"style":102,"fill":74,"textAnchor":82},"104","text-anchor:middle;font-size:11px;font-family:sans-serif","the Python console",[76,105,107],{"x":96,"y":106,"style":102,"fill":74,"textAnchor":82},"128","you, right now",[76,109,111],{"x":96,"y":110,"style":102,"fill":92,"textAnchor":82},"154","invisible in a plugin",[52,113],{"x":114,"y":87,"width":88,"height":89,"rx":90,"fill":115,"stroke":116,"style":117},"264","#eef7f4","#0f766e","stroke-width:2.5",[76,119,120],{"x":78,"y":97,"style":98,"fill":116,"textAnchor":82},"QgsMessageLog",[76,122,123],{"x":78,"y":101,"style":102,"fill":74,"textAnchor":82},"the Log Messages panel",[76,125,126],{"x":78,"y":106,"style":102,"fill":74,"textAnchor":82},"whoever debugs this later",[76,128,129],{"x":78,"y":110,"style":102,"fill":116,"textAnchor":82},"tagged, levelled, timestamped",[52,131],{"x":132,"y":87,"width":88,"height":89,"rx":90,"fill":133,"stroke":134,"style":93},"512","#eff3ff","#2563eb",[76,136,138],{"x":137,"y":97,"style":98,"fill":134,"textAnchor":82},"628","iface.messageBar()",[76,140,141],{"x":137,"y":101,"style":102,"fill":74,"textAnchor":82},"a banner over the canvas",[76,143,144],{"x":137,"y":106,"style":102,"fill":74,"textAnchor":82},"the user, right now",[76,146,147],{"x":137,"y":110,"style":102,"fill":134,"textAnchor":82},"use sparingly",[52,149],{"x":114,"y":150,"width":88,"height":87,"rx":66,"fill":151,"stroke":152,"style":93},"206","#edf8e9","#15803d",[76,154,157],{"x":78,"y":155,"style":156,"fill":152,"textAnchor":82},"228","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","messageReceived → logging",[76,159,162],{"x":78,"y":160,"style":161,"fill":74,"textAnchor":82},"246","text-anchor:middle;font-size:10px;font-family:sans-serif","for servers and scheduled jobs",[164,165],"line",{"x1":78,"y1":166,"x2":78,"y2":167,"stroke":74,"style":168},"182","200","stroke-width:2;marker-end:url(#msgArrow)",[170,171,173],"h2",{"id":172},"prerequisites","Prerequisites",[175,176,177,185,192],"ul",{},[178,179,180,184],"li",{},[181,182,183],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[178,186,187,188,191],{},"The ",[181,189,190],{},"Log Messages"," panel open (View → Panels), or the small triangle icon in the status bar's bottom-right corner.",[178,193,194,195,198],{},"For the message-bar examples, ",[17,196,197],{},"iface"," — available inside a plugin and in the Python console.",[170,200,202],{"id":201},"write-to-the-message-log","Write to the message log",[204,205,210],"pre",{"className":206,"code":207,"language":208,"meta":209,"style":209},"language-python shiki shiki-themes github-dark","from qgis.core import QgsMessageLog, Qgis\n\nQgsMessageLog.logMessage(\"Loaded 12 480 parcels\", \"ParcelTools\", Qgis.Info)\nQgsMessageLog.logMessage(\"3 features had null geometry\", \"ParcelTools\", Qgis.Warning)\nQgsMessageLog.logMessage(\"Could not open \u002Fdata\u002Fwards.gpkg\", \"ParcelTools\", Qgis.Critical)\n","python","",[17,211,212,230,237,256,271],{"__ignoreMap":209},[213,214,216,220,224,227],"span",{"class":164,"line":215},1,[213,217,219],{"class":218},"snl16","from",[213,221,223],{"class":222},"s95oV"," qgis.core ",[213,225,226],{"class":218},"import",[213,228,229],{"class":222}," QgsMessageLog, Qgis\n",[213,231,233],{"class":164,"line":232},2,[213,234,236],{"emptyLinePlaceholder":235},true,"\n",[213,238,240,243,247,250,253],{"class":164,"line":239},3,[213,241,242],{"class":222},"QgsMessageLog.logMessage(",[213,244,246],{"class":245},"sU2Wk","\"Loaded 12 480 parcels\"",[213,248,249],{"class":222},", ",[213,251,252],{"class":245},"\"ParcelTools\"",[213,254,255],{"class":222},", Qgis.Info)\n",[213,257,259,261,264,266,268],{"class":164,"line":258},4,[213,260,242],{"class":222},[213,262,263],{"class":245},"\"3 features had null geometry\"",[213,265,249],{"class":222},[213,267,252],{"class":245},[213,269,270],{"class":222},", Qgis.Warning)\n",[213,272,274,276,279,281,283],{"class":164,"line":273},5,[213,275,242],{"class":222},[213,277,278],{"class":245},"\"Could not open \u002Fdata\u002Fwards.gpkg\"",[213,280,249],{"class":222},[213,282,252],{"class":245},[213,284,285],{"class":222},", Qgis.Critical)\n",[14,287,288,291,292,295,296,249,299,249,302,305,306,309,310,313],{},[181,289,290],{},"Breakdown:"," The three arguments are message, tag and level. The ",[181,293,294],{},"tag"," becomes a tab in the Log Messages panel, so giving your plugin its own tag separates its output from the dozens of messages QGIS produces — and makes it findable when a user sends a screenshot. The levels are ",[17,297,298],{},"Qgis.Info",[17,300,301],{},"Qgis.Warning",[17,303,304],{},"Qgis.Critical"," and ",[17,307,308],{},"Qgis.Success","; ",[17,311,312],{},"Critical"," makes the log panel's status-bar indicator turn red, which is the closest thing to an alert QGIS offers without interrupting the user. Entries are timestamped automatically.",[14,315,316],{},"Keep messages specific and countable. \"Loaded 12 480 parcels\" tells a future reader whether the run was normal; \"done\" tells them nothing, and two consecutive runs of \"done\" cannot be compared.",[170,318,320],{"id":319},"choose-the-right-level","Choose the right level",[322,323,324,340],"table",{},[325,326,327],"thead",{},[328,329,330,334,337],"tr",{},[331,332,333],"th",{},"Level",[331,335,336],{},"Use it for",[331,338,339],{},"The reader's reaction",[341,342,343,356,368,380],"tbody",{},[328,344,345,350,353],{},[346,347,348],"td",{},[17,349,298],{},[346,351,352],{},"Normal progress, counts, chosen parameters",[346,354,355],{},"Reads it only when investigating",[328,357,358,362,365],{},[346,359,360],{},[17,361,308],{},[346,363,364],{},"A completed operation worth confirming",[346,366,367],{},"Reassured",[328,369,370,374,377],{},[346,371,372],{},[17,373,301],{},[346,375,376],{},"Something recoverable and unexpected — skipped rows, a fallback used",[346,378,379],{},"Investigates later",[328,381,382,386,389],{},[346,383,384],{},[17,385,304],{},[346,387,388],{},"The operation failed and produced no result",[346,390,391],{},"Acts now",[14,393,394,396],{},[181,395,290],{}," The discipline that makes levels useful is refusing to inflate them. A warning for something that happens on every run trains everyone to ignore warnings, and then the one that mattered is invisible too. If a condition is normal, log it as info; if it is genuinely unexpected, warn once per run rather than once per feature.",[170,398,400],{"id":399},"speak-to-the-user-with-the-message-bar","Speak to the user with the message bar",[14,402,403],{},"The log is for diagnosis. When the user needs to know something now, use the message bar.",[204,405,407],{"className":206,"code":406,"language":208,"meta":209,"style":209},"from qgis.core import Qgis\nfrom qgis.utils import iface\n\niface.messageBar().pushMessage(\n    \"Parcels\",\n    \"12 480 features loaded; 3 had null geometry\",\n    level=Qgis.Warning,\n    duration=8,\n)\n",[17,408,409,420,432,436,441,449,457,470,483],{"__ignoreMap":209},[213,410,411,413,415,417],{"class":164,"line":215},[213,412,219],{"class":218},[213,414,223],{"class":222},[213,416,226],{"class":218},[213,418,419],{"class":222}," Qgis\n",[213,421,422,424,427,429],{"class":164,"line":232},[213,423,219],{"class":218},[213,425,426],{"class":222}," qgis.utils ",[213,428,226],{"class":218},[213,430,431],{"class":222}," iface\n",[213,433,434],{"class":164,"line":239},[213,435,236],{"emptyLinePlaceholder":235},[213,437,438],{"class":164,"line":258},[213,439,440],{"class":222},"iface.messageBar().pushMessage(\n",[213,442,443,446],{"class":164,"line":273},[213,444,445],{"class":245},"    \"Parcels\"",[213,447,448],{"class":222},",\n",[213,450,452,455],{"class":164,"line":451},6,[213,453,454],{"class":245},"    \"12 480 features loaded; 3 had null geometry\"",[213,456,448],{"class":222},[213,458,460,464,467],{"class":164,"line":459},7,[213,461,463],{"class":462},"s9osk","    level",[213,465,466],{"class":218},"=",[213,468,469],{"class":222},"Qgis.Warning,\n",[213,471,473,476,478,481],{"class":164,"line":472},8,[213,474,475],{"class":462},"    duration",[213,477,466],{"class":218},[213,479,66],{"class":480},"sDLfK",[213,482,448],{"class":222},[213,484,486],{"class":164,"line":485},9,[213,487,488],{"class":222},")\n",[14,490,491,493,494,497,498,500,501,504],{},[181,492,290],{}," The first argument is a bold prefix and the second the body. ",[17,495,496],{},"duration"," is in seconds; passing ",[17,499,54],{}," makes the message persist until dismissed, which is right for an error and rude for a success. The message bar is modeless — it does not steal focus or block work — which is exactly why it should be preferred over ",[17,502,503],{},"QMessageBox"," for anything that is not a question. One bar message per user action is a good ceiling; the details belong in the log, and pointing at the log in the bar text is a reasonable way to connect them.",[14,506,507],{},[37,508,511,514,517,520,523,532,537,540,544,549,554,556,560,563,567,569,572,575,579,581,584,588,591,593],{"viewBox":509,"role":40,"ariaLabel":510,"xmlns":42},"0 0 760 244","Decision guide mapping a situation to the right output: user action needed goes to the message bar, diagnostics go to the message log, and unattended runs forward the log into Python logging",[44,512,513],{},"Choosing where a message belongs",[48,515,516],{},"If the user must act, use the message bar with a short body and details in the log. If the message is diagnostic, write it to the message log with your plugin's tag. If the code runs unattended, forward the message log into Python logging so the entries reach a file or the journal. Print is only for interactive experimentation.",[52,518],{"x":54,"y":54,"width":55,"height":519,"fill":57},"244",[76,521,522],{"x":78,"y":79,"style":80,"fill":81,"textAnchor":82},"One question decides it: who acts on this?",[52,524],{"x":86,"y":525,"width":526,"height":527,"rx":528,"fill":529,"stroke":530,"style":531},"48","236","30","6","#efeadd","#59645f","stroke-width:1.5",[76,533,536],{"x":534,"y":535,"style":156,"fill":81,"textAnchor":82},"134","68","the situation",[52,538],{"x":114,"y":525,"width":539,"height":527,"rx":528,"fill":529,"stroke":530,"style":531},"480",[76,541,543],{"x":542,"y":535,"style":156,"fill":81,"textAnchor":82},"504","where it goes",[52,545],{"x":86,"y":546,"width":526,"height":547,"rx":528,"fill":548,"stroke":530,"style":531},"84","36","#fffdf7",[76,550,553],{"x":527,"y":551,"style":552,"fill":74},"107","font-size:11px;font-family:sans-serif","the user must do something",[52,555],{"x":114,"y":546,"width":539,"height":547,"rx":528,"fill":133,"stroke":134,"style":93},[76,557,559],{"x":558,"y":551,"style":552,"fill":74},"278","message bar, short body, details in the log",[52,561],{"x":86,"y":562,"width":526,"height":547,"rx":528,"fill":548,"stroke":530,"style":531},"126",[76,564,566],{"x":527,"y":565,"style":552,"fill":74},"149","someone may debug this later",[52,568],{"x":114,"y":562,"width":539,"height":547,"rx":528,"fill":115,"stroke":116,"style":93},[76,570,571],{"x":558,"y":565,"style":552,"fill":74},"message log, your own tag, honest level",[52,573],{"x":86,"y":574,"width":526,"height":547,"rx":528,"fill":548,"stroke":530,"style":531},"168",[76,576,578],{"x":527,"y":577,"style":552,"fill":74},"191","nobody is watching at all",[52,580],{"x":114,"y":574,"width":539,"height":547,"rx":528,"fill":151,"stroke":152,"style":93},[76,582,583],{"x":558,"y":577,"style":552,"fill":74},"forward messageReceived into Python logging",[52,585],{"x":86,"y":586,"width":526,"height":587,"rx":528,"fill":548,"stroke":530,"style":531},"210","26",[76,589,590],{"x":527,"y":155,"style":552,"fill":74},"you, in the console, now",[52,592],{"x":114,"y":586,"width":539,"height":587,"rx":528,"fill":91,"stroke":92,"style":531},[76,594,595],{"x":558,"y":155,"style":552,"fill":74},"print() — and delete it before committing",[170,597,599],{"id":598},"read-what-qgis-is-telling-you","Read what QGIS is telling you",[14,601,602],{},"The most useful messages in the log are usually not yours. Providers, algorithms and plugins all write there, and the answer to \"why did that layer fail to load\" is frequently already sitting in the panel.",[204,604,606],{"className":206,"code":605,"language":208,"meta":209,"style":209},"from qgis.core import QgsApplication\n\ndef on_message(message, tag, level):\n    if level >= Qgis.Warning:\n        print(f\"[{tag}] {message}\")\n\nQgsApplication.messageLog().messageReceived.connect(on_message)\n",[17,607,608,619,623,635,649,686,690],{"__ignoreMap":209},[213,609,610,612,614,616],{"class":164,"line":215},[213,611,219],{"class":218},[213,613,223],{"class":222},[213,615,226],{"class":218},[213,617,618],{"class":222}," QgsApplication\n",[213,620,621],{"class":164,"line":232},[213,622,236],{"emptyLinePlaceholder":235},[213,624,625,628,632],{"class":164,"line":239},[213,626,627],{"class":218},"def",[213,629,631],{"class":630},"svObZ"," on_message",[213,633,634],{"class":222},"(message, tag, level):\n",[213,636,637,640,643,646],{"class":164,"line":258},[213,638,639],{"class":218},"    if",[213,641,642],{"class":222}," level ",[213,644,645],{"class":218},">=",[213,647,648],{"class":222}," Qgis.Warning:\n",[213,650,651,654,657,660,663,666,668,671,674,676,679,681,684],{"class":164,"line":273},[213,652,653],{"class":480},"        print",[213,655,656],{"class":222},"(",[213,658,659],{"class":218},"f",[213,661,662],{"class":245},"\"[",[213,664,665],{"class":480},"{",[213,667,294],{"class":222},[213,669,670],{"class":480},"}",[213,672,673],{"class":245},"] ",[213,675,665],{"class":480},[213,677,678],{"class":222},"message",[213,680,670],{"class":480},[213,682,683],{"class":245},"\"",[213,685,488],{"class":222},[213,687,688],{"class":164,"line":451},[213,689,236],{"emptyLinePlaceholder":235},[213,691,692],{"class":164,"line":459},[213,693,694],{"class":222},"QgsApplication.messageLog().messageReceived.connect(on_message)\n",[14,696,697,699,700,703,704,707,708,710,711,715],{},[181,698,290],{}," Connecting to ",[17,701,702],{},"messageReceived"," gives you every entry as it is written, including QGIS's own. Filtering by level keeps the noise down while you are hunting a specific failure. This is also the hook that makes headless runs observable — replacing the ",[17,705,706],{},"print"," with a call into Python's ",[17,709,32],{}," sends provider errors to the same file as everything else, which is the pattern developed in ",[25,712,714],{"href":713},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fhandle-errors-and-logging-in-unattended-scripts\u002F","Handle Errors and Logging in Unattended Scripts",".",[170,717,719],{"id":718},"log-from-a-processing-algorithm","Log from a Processing algorithm",[14,721,722,723,715],{},"Inside a custom algorithm the feedback object is the correct channel, because it routes to whichever front end is running — the dialog, the console, or ",[17,724,725],{},"qgis_process",[204,727,729],{"className":206,"code":728,"language":208,"meta":209,"style":209},"def processAlgorithm(self, parameters, context, feedback):\n    feedback.pushInfo(f\"Processing {source.featureCount()} features\")\n    if skipped:\n        feedback.reportError(f\"{skipped} features had invalid geometry\", fatalError=False)\n    return {\"OUTPUT\": sink_id}\n",[17,730,731,741,763,770,801],{"__ignoreMap":209},[213,732,733,735,738],{"class":164,"line":215},[213,734,627],{"class":218},[213,736,737],{"class":630}," processAlgorithm",[213,739,740],{"class":222},"(self, parameters, context, feedback):\n",[213,742,743,746,748,751,753,756,758,761],{"class":164,"line":232},[213,744,745],{"class":222},"    feedback.pushInfo(",[213,747,659],{"class":218},[213,749,750],{"class":245},"\"Processing ",[213,752,665],{"class":480},[213,754,755],{"class":222},"source.featureCount()",[213,757,670],{"class":480},[213,759,760],{"class":245}," features\"",[213,762,488],{"class":222},[213,764,765,767],{"class":164,"line":239},[213,766,639],{"class":218},[213,768,769],{"class":222}," skipped:\n",[213,771,772,775,777,779,781,784,786,789,791,794,796,799],{"class":164,"line":258},[213,773,774],{"class":222},"        feedback.reportError(",[213,776,659],{"class":218},[213,778,683],{"class":245},[213,780,665],{"class":480},[213,782,783],{"class":222},"skipped",[213,785,670],{"class":480},[213,787,788],{"class":245}," features had invalid geometry\"",[213,790,249],{"class":222},[213,792,793],{"class":462},"fatalError",[213,795,466],{"class":218},[213,797,798],{"class":480},"False",[213,800,488],{"class":222},[213,802,803,806,809,812],{"class":164,"line":273},[213,804,805],{"class":218},"    return",[213,807,808],{"class":222}," {",[213,810,811],{"class":245},"\"OUTPUT\"",[213,813,814],{"class":222},": sink_id}\n",[14,816,817,819,820,305,823,826,827,829,830,832,833,836,837,715],{},[181,818,290],{}," ",[17,821,822],{},"pushInfo()",[17,824,825],{},"reportError()"," reach the user wherever the algorithm was launched from, which ",[17,828,120],{}," does not — a message logged to the panel during a ",[17,831,725],{}," run may never be seen. ",[17,834,835],{},"fatalError=False"," reports a problem without aborting, which suits a per-feature complaint. Writing an algorithm this way is what makes it usable from the toolbox, from a model and from the command line, as covered in ",[25,838,840],{"href":839},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002Fwrite-custom-processing-algorithm-pyqgis\u002F","Write a Custom Processing Algorithm in PyQGIS",[170,842,844],{"id":843},"write-messages-that-can-be-searched-later","Write messages that can be searched later",[14,846,847],{},"A log is read under pressure, usually months after it was written, often by someone else. Two habits make that reading fast.",[14,849,850,853],{},[181,851,852],{},"Put the numbers in, and keep the shape constant."," A message built as prose varies from run to run and cannot be compared; a message built from fixed keys and changing values can be grepped, diffed and even parsed.",[204,855,857],{"className":206,"code":856,"language":208,"meta":209,"style":209},"def log_summary(tag, **fields):\n    body = \" \".join(f\"{key}={value}\" for key, value in sorted(fields.items()))\n    QgsMessageLog.logMessage(body, tag, Qgis.Info)\n\nlog_summary(\"ParcelTools\", stage=\"load\", source=\"parcels\", features=12480, seconds=3.4)\nlog_summary(\"ParcelTools\", stage=\"buffer\", distance=25, features=12480, seconds=41.2)\n",[17,858,859,875,925,930,934,983],{"__ignoreMap":209},[213,860,861,863,866,869,872],{"class":164,"line":215},[213,862,627],{"class":218},[213,864,865],{"class":630}," log_summary",[213,867,868],{"class":222},"(tag, ",[213,870,871],{"class":218},"**",[213,873,874],{"class":222},"fields):\n",[213,876,877,880,882,885,888,890,892,894,897,899,901,903,906,908,910,913,916,919,922],{"class":164,"line":232},[213,878,879],{"class":222},"    body ",[213,881,466],{"class":218},[213,883,884],{"class":245}," \" \"",[213,886,887],{"class":222},".join(",[213,889,659],{"class":218},[213,891,683],{"class":245},[213,893,665],{"class":480},[213,895,896],{"class":222},"key",[213,898,670],{"class":480},[213,900,466],{"class":245},[213,902,665],{"class":480},[213,904,905],{"class":222},"value",[213,907,670],{"class":480},[213,909,683],{"class":245},[213,911,912],{"class":218}," for",[213,914,915],{"class":222}," key, value ",[213,917,918],{"class":218},"in",[213,920,921],{"class":480}," sorted",[213,923,924],{"class":222},"(fields.items()))\n",[213,926,927],{"class":164,"line":239},[213,928,929],{"class":222},"    QgsMessageLog.logMessage(body, tag, Qgis.Info)\n",[213,931,932],{"class":164,"line":258},[213,933,236],{"emptyLinePlaceholder":235},[213,935,936,939,941,943,946,948,951,953,956,958,961,963,966,968,971,973,976,978,981],{"class":164,"line":273},[213,937,938],{"class":222},"log_summary(",[213,940,252],{"class":245},[213,942,249],{"class":222},[213,944,945],{"class":462},"stage",[213,947,466],{"class":218},[213,949,950],{"class":245},"\"load\"",[213,952,249],{"class":222},[213,954,955],{"class":462},"source",[213,957,466],{"class":218},[213,959,960],{"class":245},"\"parcels\"",[213,962,249],{"class":222},[213,964,965],{"class":462},"features",[213,967,466],{"class":218},[213,969,970],{"class":480},"12480",[213,972,249],{"class":222},[213,974,975],{"class":462},"seconds",[213,977,466],{"class":218},[213,979,980],{"class":480},"3.4",[213,982,488],{"class":222},[213,984,985,987,989,991,993,995,998,1000,1003,1005,1008,1010,1012,1014,1016,1018,1020,1022,1025],{"class":164,"line":451},[213,986,938],{"class":222},[213,988,252],{"class":245},[213,990,249],{"class":222},[213,992,945],{"class":462},[213,994,466],{"class":218},[213,996,997],{"class":245},"\"buffer\"",[213,999,249],{"class":222},[213,1001,1002],{"class":462},"distance",[213,1004,466],{"class":218},[213,1006,1007],{"class":480},"25",[213,1009,249],{"class":222},[213,1011,965],{"class":462},[213,1013,466],{"class":218},[213,1015,970],{"class":480},[213,1017,249],{"class":222},[213,1019,975],{"class":462},[213,1021,466],{"class":218},[213,1023,1024],{"class":480},"41.2",[213,1026,488],{"class":222},[14,1028,1029,1031,1032,1035,1036,1039],{},[181,1030,290],{}," Sorting the keys means the same fields always appear in the same order, so two runs line up when diffed. Using ",[17,1033,1034],{},"key=value"," pairs rather than a sentence makes ",[17,1037,1038],{},"grep 'stage=buffer' | awk"," viable, which is the difference between answering \"did this get slower?\" in ten seconds and re-running the job to find out. Keeping the tag constant per plugin, rather than varying it per operation, keeps everything in one tab of the log panel where it can be read as a sequence.",[14,1041,1042,1045],{},[181,1043,1044],{},"Log the decision, not just the outcome."," \"Skipped 3 features\" prompts an immediate second question; \"skipped=3 reason=null_geometry ids=104,220,881\" answers it. Where the identifiers are few, include them — a message that lets someone open exactly the three offending features has saved a round trip that would otherwise take a morning.",[14,1047,1048],{},[37,1049,1052,1055,1058,1060,1063,1067,1072,1077,1082,1085,1089,1091,1095,1098,1101,1104,1108,1110,1113,1115,1118],{"viewBox":1050,"role":40,"ariaLabel":1051,"xmlns":42},"0 0 760 236","Two log excerpts compared: prose messages that cannot be searched, and key-value messages whose fixed shape lets two runs be diffed",[44,1053,1054],{},"Prose reads once; key-value pairs read forever",[48,1056,1057],{},"Prose log lines describe what happened in different words each run, so two runs cannot be compared. Key-value lines keep the same field names in the same order with different values, so a grep isolates one stage and a diff between two nights shows exactly which number changed.",[52,1059],{"x":54,"y":54,"width":55,"height":526,"fill":57},[76,1061,1062],{"x":78,"y":79,"style":80,"fill":81,"textAnchor":82},"Written for the person reading it in three months",[52,1064],{"x":86,"y":525,"width":1065,"height":1066,"rx":90,"fill":91,"stroke":92,"style":117},"356","152",[76,1068,1071],{"x":1069,"y":1070,"style":98,"fill":92,"textAnchor":82},"194","74","prose",[52,1073],{"x":547,"y":1074,"width":1075,"height":587,"rx":1076,"fill":548,"stroke":530,"style":531},"88","316","4",[76,1078,1081],{"x":525,"y":1079,"style":1080,"fill":74},"106","font-size:10px;font-family:sans-serif","Loaded the parcels layer OK",[52,1083],{"x":547,"y":1084,"width":1075,"height":587,"rx":1076,"fill":548,"stroke":530,"style":531},"120",[76,1086,1088],{"x":525,"y":1087,"style":1080,"fill":74},"138","Buffering took a while today",[52,1090],{"x":547,"y":1066,"width":1075,"height":587,"rx":1076,"fill":548,"stroke":530,"style":531},[76,1092,1094],{"x":525,"y":1093,"style":1080,"fill":74},"170","Finished, some features skipped",[52,1096],{"x":1097,"y":525,"width":1065,"height":1066,"rx":90,"fill":151,"stroke":152,"style":117},"388",[76,1099,1034],{"x":1100,"y":1070,"style":98,"fill":152,"textAnchor":82},"566",[52,1102],{"x":1103,"y":1074,"width":1075,"height":587,"rx":1076,"fill":548,"stroke":530,"style":531},"408",[76,1105,1107],{"x":1106,"y":1079,"style":1080,"fill":74},"420","stage=load features=12480 seconds=3.4",[52,1109],{"x":1103,"y":1084,"width":1075,"height":587,"rx":1076,"fill":548,"stroke":530,"style":531},[76,1111,1112],{"x":1106,"y":1087,"style":1080,"fill":74},"stage=buffer features=12480 seconds=41.2",[52,1114],{"x":1103,"y":1066,"width":1075,"height":587,"rx":1076,"fill":548,"stroke":530,"style":531},[76,1116,1117],{"x":1106,"y":1093,"style":1080,"fill":74},"skipped=3 reason=null_geometry ids=104,220,881",[76,1119,1121],{"x":78,"y":1120,"style":102,"fill":530,"textAnchor":82},"224","Constant field names in a constant order are what make two nights comparable",[170,1123,1125],{"id":1124},"qgis-version-compatibility","QGIS version compatibility",[14,1127,1128,1129,1131],{},"The examples target ",[181,1130,183],{}," (Python 3.12).",[322,1133,1134,1147],{},[325,1135,1136],{},[328,1137,1138,1141,1144],{},[331,1139,1140],{},"QGIS version",[331,1142,1143],{},"Python",[331,1145,1146],{},"Notes",[341,1148,1149,1163,1177,1188],{},[328,1150,1151,1154,1157],{},[346,1152,1153],{},"3.22 LTR",[346,1155,1156],{},"3.9",[346,1158,1159,1162],{},[17,1160,1161],{},"QgsMessageLog.logMessage"," and levels identical.",[328,1164,1165,1168,1170],{},[346,1166,1167],{},"3.28 LTR",[346,1169,1156],{},[346,1171,1172,1173,1176],{},"Adds ",[17,1174,1175],{},"notifyUser"," argument to control the status-bar indicator.",[328,1178,1179,1182,1185],{},[346,1180,1181],{},"3.34 LTR",[346,1183,1184],{},"3.12",[346,1186,1187],{},"Baseline for this page.",[328,1189,1190,1193,1195],{},[346,1191,1192],{},"3.40 \u002F 3.44",[346,1194,1184],{},[346,1196,1197,1200,1201,1204],{},[17,1198,1199],{},"Qgis.MessageLevel"," members are scoped (",[17,1202,1203],{},"Qgis.MessageLevel.Warning","); the short spelling still resolves.",[170,1206,1208],{"id":1207},"troubleshooting","Troubleshooting",[175,1210,1211,1217,1227,1239,1247,1253],{},[178,1212,1213,1216],{},[181,1214,1215],{},"Nothing appears in the panel."," The tag has its own tab — check the tab strip along the bottom of the Log Messages panel rather than only the General tab.",[178,1218,1219,1224,1225,715],{},[181,1220,1221,1223],{},[17,1222,19],{}," output has vanished."," The code is not running in the console. Switch to ",[17,1226,120],{},[178,1228,1229,1232,1233,1235,1236,1238],{},[181,1230,1231],{},"The message bar notice disappears too quickly."," Raise ",[17,1234,496],{},", or pass ",[17,1237,54],{}," for anything the user must acknowledge.",[178,1240,1241,1246],{},[181,1242,1243,1245],{},[17,1244,197],{}," is not defined."," You are outside the QGIS GUI. There is no message bar in a headless run; log instead.",[178,1248,1249,1252],{},[181,1250,1251],{},"The log fills with one message per feature."," Aggregate: count the occurrences and log once at the end with the total.",[178,1254,1255,1258,1259,1261,1262,1265],{},[181,1256,1257],{},"Messages from a background task never appear."," They do, but ",[17,1260,120],{}," calls from a worker thread are queued; if the task crashed before returning, the queue may not have flushed. Log from ",[17,1263,1264],{},"finished()"," where possible.",[170,1267,1269],{"id":1268},"conclusion","Conclusion",[14,1271,1272,1273,1276,1277,1279,1280,1282],{},"Use ",[17,1274,1275],{},"QgsMessageLog.logMessage()"," with your own tag and an honest level for anything diagnostic, ",[17,1278,138],{}," for the one thing the user needs to know, and a Processing feedback object inside algorithms. Connect to ",[17,1281,702],{}," when you need to see what QGIS itself is reporting, or to forward everything into Python logging for a run nobody is watching.",[170,1284,1286],{"id":1285},"frequently-asked-questions","Frequently Asked Questions",[14,1288,1289,1292],{},[181,1290,1291],{},"Where is the log stored on disk?","\nIt is not — the panel is in-memory and cleared when QGIS closes. For a persistent record, forward the messages into Python logging and write them to a file.",[14,1294,1295,1298],{},[181,1296,1297],{},"Can I clear the log from Python?","\nNot directly; the panel provides a clear action in its interface. A plugin that needs a clean slate should use its own tag so its entries are separable anyway.",[14,1300,1301,1304],{},[181,1302,1303],{},"Should a plugin log on every action?","\nLog the ones with consequences — files written, features changed, parameters chosen — at info level. Skip narration of trivial steps; a log nobody can skim is a log nobody reads.",[14,1306,1307,1310,1311,715],{},[181,1308,1309],{},"How do I show a progress message that updates?","\nThe message bar can host widgets, including a progress bar. That pattern is in ",[25,1312,1314],{"href":1313},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fshow-plugin-progress-and-cancellation-pyqgis\u002F","Show Progress and Support Cancellation in a QGIS Plugin",[14,1316,1317,1320],{},[181,1318,1319],{},"Is logging slow?","\nNot at a sane rate. It becomes slow when called once per feature over a large layer, which is a reason to aggregate rather than a reason to avoid logging.",[170,1322,1324],{"id":1323},"related","Related",[175,1326,1327,1332,1338,1342,1348],{},[178,1328,1329,1331],{},[25,1330,28],{"href":27}," — the guide this recipe belongs to",[178,1333,1334],{},[25,1335,1337],{"href":1336},"\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Ffixing-pyqgis-module-import-errors\u002F","Fixing PyQGIS Module Import Errors",[178,1339,1340],{},[25,1341,714],{"href":713},[178,1343,1344],{},[25,1345,1347],{"href":1346},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fprofile-slow-pyqgis-code\u002F","Profile Slow PyQGIS Code",[178,1349,1350],{},[25,1351,1353],{"href":1352},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console Basics",[1355,1356,1357],"style",{},"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 .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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":209,"searchDepth":232,"depth":232,"links":1359},[1360,1361,1362,1363,1364,1365,1366,1367,1368,1369,1370,1371],{"id":172,"depth":232,"text":173},{"id":201,"depth":232,"text":202},{"id":319,"depth":232,"text":320},{"id":399,"depth":232,"text":400},{"id":598,"depth":232,"text":599},{"id":718,"depth":232,"text":719},{"id":843,"depth":232,"text":844},{"id":1124,"depth":232,"text":1125},{"id":1207,"depth":232,"text":1208},{"id":1268,"depth":232,"text":1269},{"id":1285,"depth":232,"text":1286},{"id":1323,"depth":232,"text":1324},"Use QgsMessageLog and the message bar properly — pick tags and levels, read provider messages, connect a handler, and know when print() is the wrong tool.","md",{"slug":1375,"type":1376,"breadcrumb":1377,"datePublished":1378,"dateModified":1378},"log-messages-to-qgis-message-log-pyqgis","article","QGIS Message Log","2026-08-10","\u002Fpyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Flog-messages-to-qgis-message-log-pyqgis",{"title":5,"description":1372},"pyqgis-fundamentals-environment-setup\u002Fdebugging-pyqgis-scripts\u002Flog-messages-to-qgis-message-log-pyqgis\u002Findex","d7ybomPYc3oMeMvRQ5p0Xs8XCu7HgitXSGIR36JQLo0",1786401337550]