{"id":5317,"date":"2026-09-18T01:56:15","date_gmt":"2026-09-18T04:56:15","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/09\/18\/logpoints-walkthrough\/"},"modified":"2026-09-18T01:56:15","modified_gmt":"2026-09-18T04:56:15","slug":"logpoints-walkthrough","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/09\/18\/logpoints-walkthrough\/","title":{"rendered":"Logpoints Walkthrough"},"content":{"rendered":"<div>\n<div>\n<section data-clarity-region=\"article\">\n<div>\n<p><a href=\"\/idea\/category\/idea\/\">IntelliJ IDEA<\/a><\/p>\n<p>Modern development tools, especially IntelliJ IDEA, have such comprehensive debugging support that for virtually any niche use case, there is a specialized tool for the job. This can make it hard to know where to begin. If you are new to debugging tools and want the biggest return on your learning investment, the best feature to start with is logpoints.<\/p>\n<p>Even though working with them is as simple as debugging with plain println statements (and arguably simpler), they <em>vastly<\/em> broaden the range of issues that you can debug. For some issues, logpoints are the only practical approach. And for the rest, logpoints provide a convenience that saves a lot of time and effort.<\/p>\n<p>Also, in IntelliJ IDEA 2026.2, logpoints got some <a href=\"https:\/\/blog.jetbrains.com\/idea\/2026\/08\/println-debugging-done-right\/\">very cool improvements<\/a>, so it\u2019s a perfect time to explore them.<\/p>\n<h2><strong>Our example problem<\/strong><\/h2>\n<p>Here\u2019s a <a href=\"https:\/\/github.com\/flounder4130\/grpc-timeout\" target=\"_blank\" rel=\"noopener\">mini client and server<\/a> that use <a href=\"https:\/\/grpc.io\/\" target=\"_blank\" rel=\"noopener\">gRPC<\/a> for communication. The server has a bug that makes it return incorrect discount values for some tenants.<\/p>\n<p>So we\u2019ll follow the usual debugging flow. We\u2019ll reproduce the problem, set up visibility into the inner workings of the server, send a problematic request, and observe exactly how it produces the wrong result.<\/p>\n<h2><strong>Reproducing the bug<\/strong><\/h2>\n<p>To simulate running in another environment, the project bundles a Dockerfile with the listening and debug ports exposed. You can launch it using the supplied <kbd>GrpcQuoteServerContainer<\/kbd> run configuration or directly from the command line:<\/p>\n<pre data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">docker build -t grpc-timeout . docker run --rm -p 50051:50051 -p 5005:5005 grpc-timeout<\/pre>\n<p>Then for a problematic request, use the <kbd>GrpcQuoteClientLoop<\/kbd> run configuration, which will periodically query the server. This lets us forget about sending requests manually and focus more on what happens on the server.<\/p>\n<p>When both the server and the client loop are running, the console shows the following:<\/p>\n<pre data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=100.00 USD source=live detail=region=emea, discount_bps=0<\/pre>\n<p>But we would expect to see:<\/p>\n<pre data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">tenant='JetBrains' region='EMEA' status=OK symbol=IDEA price=80.00 USD source=live detail=region=emea, discount_bps=2000<\/pre>\n<h2><strong>Attaching to the server<\/strong><\/h2>\n<p>The server is not launched from a local IntelliJ IDEA debug session, but it listens for debugger connections, so we can still attach to it using the provided <kbd>GrpcQuoteServer<\/kbd> attach run configuration.<\/p>\n<p>It\u2019s worth noting that, for the debugger, there is no difference whether the process runs locally, in a separate environment, or on a remote host. In all three cases, the communication happens over a socket, so our exercise is valid for debugging any Java process regardless of locality.<\/p>\n<h2><strong>Logpoints<\/strong><\/h2>\n<p>Logpoints are similar to <kbd>println<\/kbd> statements in that they don\u2019t suspend the program but only log the necessary details to the console. Unlike <kbd>println<\/kbd> statements, however, they can be changed without rebuilding or redeploying the application.<\/p>\n<p>You might already know how to set a logpoint, but IntelliJ IDEA 2026.2 introduced a faster way. Click in the gutter in between any two executable lines and enter the expression you want to log. As the starting point, we can use the beginning of the query handling method ( <kbd>QuoteEndpoint:12 <\/kbd>):<\/p>\n<figure><\/figure>\n<p><strong>Warning:<\/strong> Beware of heavy computations in hot paths. They are executed in the same VM and are not magically free. As of version 2026.2, IntelliJ IDEA removes the debugger-introduced overhead through instrumentation, but heavy logging expressions may still take time to execute.<\/p>\n<p>For every incoming request, the console now prints:<\/p>\n<pre data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">EMEA JetBrains<\/pre>\n<p>Now, with the request loop running, we can progressively change and add logpoints until the output points to the bug. Just add more logpoints or update the existing ones and watch for new messages in the console as new requests come in.<\/p>\n<figure>\n<\/figure>\n<p>After following the call chain, which allows us to rule out any initial suspicions we might have had, we arrive at the <kbd>discountBpsFor()<\/kbd> method:<\/p>\n<figure><\/figure>\n<p>The console points at the tenant name not being properly normalized:<\/p>\n<pre data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">tenant = JetBrains expected: jetbrains<\/pre>\n<p>Also, the absence of discount applied tells us that the block with the correct discount is never entered. Normalizing the tenant name should fix the bug:<\/p>\n<pre data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">private static int discountBpsFor(String tenant) { if (\"jetbrains\".equalsIgnoreCase(tenant)) { return 2_000; } &nbsp;&nbsp;&nbsp;&nbsp;return 0; }<\/pre>\n<p><strong>Pro tip<\/strong>: When in doubt about what produced specific console output, click the line in the console, and IntelliJ IDEA will take you to the relevant logpoint or piece of code:<\/p>\n<figure><\/figure>\n<p>Even if you are using <kbd>println<\/kbd> statements for logging, the navigation will work for you as long as you are running the process with IntelliJ IDEA\u2019s debugger.<\/p>\n<h2><strong>Testing the fix<\/strong><\/h2>\n<p>Let\u2019s test the fix while we\u2019re here. Logpoints are meant for logging, not for modifying the program, but nothing really prevents us from testing how a particular fix would behave:<\/p>\n<figure><\/figure>\n<p>The prototype works as expected:<\/p>\n<pre data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">EMEA JetBrains jetbrains discount applied<\/pre>\n<p>Now that we\u2019ve tested the fix, we can change the actual server code.<\/p>\n<h2><strong>Why not use <\/strong><strong>println<\/strong><strong> statements?<\/strong><\/h2>\n<p>You\u2019re probably thinking logpoints look like nicer println statements. And they do, in a sense, because the core mechanic is the same. In both cases, you <strong>add probes in a simple way that doesn\u2019t affect how the program runs<\/strong>.<\/p>\n<p>Despite their similarities, there are several reasons why logpoints can be a better choice:<\/p>\n<ul>\n<li>They don\u2019t clutter the code, meaning you won\u2019t spend effort on cleaning up (and won\u2019t commit a stray one to production).<\/li>\n<li>They are flexible about what they log and when. For example, if you only want to sample frequent events, this is <a href=\"https:\/\/flounder.dev\/posts\/logpoints-tricks\/#logging-frequent-events\" target=\"_blank\" rel=\"noopener\">how you configure it<\/a>.<\/li>\n<li>They allow you to insert logging inside your dependencies (we\u2019ll do that shortly).<\/li>\n<li>Most importantly for this scenario, they can save you costly redeploys. Rerunning a local Docker container just to add logging in a toy project might be acceptable, but in large real-world projects it often is not.<\/li>\n<\/ul>\n<p>These advantages set logpoints apart from println statements and make them feel more like the professional debugging tool they are.<\/p>\n<h2><strong>Why not use regular breakpoints?<\/strong><\/h2>\n<p>When using the debugger, most developers will reach for breakpoints. But in the particular scenario we\u2019ve been looking at, logpoints are a better fit, and this is not just a matter of preference.<\/p>\n<p>Let\u2019s see what happens if we use regular breakpoints. After attaching to the server, set a line breakpoint at <kbd>GrpcQuoteServer.java:55<\/kbd> . The next request from the loop suspends the server:<\/p>\n<figure><\/figure>\n<p>But after looking at the program state and stepping a couple of times, we find ourselves in the cancellation path:<\/p>\n<figure><\/figure>\n<p>Once we\u2019re there, there is no useful information, because it\u2019s not where requests normally end up. This happens because the deadline that the client has set is expired and the server discards further work on the request. You can see that IntelliJ IDEA greyed out parts of the code that are not going to be executed. To bring the problematic state back, we have to send requests one after another and do our debugging work within the timeout window.<\/p>\n<p>This happens because our client sets a deadline for the remote call. Unlike a typical HTTP\/REST client timeout, where the timeout only signals failure on the client, gRPC can propagate the client\u2019s deadline to the server. As a result, the client can actually cancel the server-side work, not just stop waiting for a response.<\/p>\n<p>On the other hand, logpoints give us the same information we would get in the debugger UI, except that we observe it in the console. Importantly, using them doesn\u2019t suspend the server, so we can extract the information we need without triggering the timeout.<\/p>\n<h2><strong>Bonus: Removing the timeout<\/strong><\/h2>\n<p>If you\u2019d prefer an alternative approach to this scenario, here\u2019s another way to debug it. For our gRPC example, the problematic bit was the timeout, and we can remove it at runtime using\u2026 logpoints!<\/p>\n<p>As you\u2019ve just seen, logpoint expressions can modify the running program through side effects. Here, we can use this technique to adjust the incoming request.<\/p>\n<p>First, find the library method that sets the timeout. There are several methods we can use. One of them is <kbd>io.grpc.internal.ServerImpl.createContext<\/kbd> :<\/p>\n<figure><\/figure>\n<p>In that method, we can rewrite the value of the <kbd>timeoutNanos<\/kbd> local variable right after it has been assigned:<\/p>\n<figure><\/figure>\n<p>With this logpoint in place, every time the gRPC timeout value is read from the request headers, it is immediately replaced with a five-minute deadline. This means we can suspend the server again.<\/p>\n<p>If you want to only extend the timeout for the reproducer requests and keep the server functioning as usual \u2013 for example, if it runs on a shared staging instance \u2013 you can use multiline logic inside a logpoint:<\/p>\n<figure><\/figure>\n<p>Here\u2019s the code to copy to the logpoint:<\/p>\n<pre data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Metadata.Key&lt;String&gt; DEBUG_HEADER = Metadata.Key.of(\"Debug\", Metadata.ASCII_STRING_MARSHALLER); String debugHeader = headers.get(DEBUG_HEADER); if (\"Debug\".equals(debugHeader)) { timeoutNanos = java.util.concurrent.TimeUnit.MINUTES.toNanos(5L); return \"Timeout reset\"; }<\/pre>\n<p>The multiline expression parses the request headers and extends the timeout only for requests with the <kbd>Debug<\/kbd> header (which our test clients add). Other requests keep the normal deadline. The if branch returns \u201cTimeout reset\u201d, confirming when the branch was visited.<\/p>\n<p><strong>Pro tip:<\/strong> By combining logpoints with the Mark Object feature, you can access arbitrary objects in the logpoint expression field. <a href=\"https:\/\/flounder.dev\/posts\/debugger-god-mode\/\" target=\"_blank\" rel=\"noopener\">This article<\/a> covers the technique in more detail.<\/p>\n<h2>The ij-debugger AI agent skill<\/h2>\n<p>Of course, the method above requires familiarity with the library or time to explore it. If you have neither and just want to change the runtime behavior quickly, you can delegate the task to an AI agent using the bundled <kbd>ij-debugger<\/kbd> <a href=\"https:\/\/www.jetbrains.com\/help\/idea\/logpoints.html#ai_agent_skill\" target=\"_blank\" rel=\"noopener\">AI agent skill<\/a>:<\/p>\n<figure><\/figure>\n<figure><\/figure>\n<p>The AI agent follows the same path we followed manually, finding where the deadline is read and producing a logpoint expression that changes only the requests we care about. So even without knowing the gRPC internals upfront, we can still get to a targeted workaround and continue debugging.<\/p>\n<h2><strong>Conclusion<\/strong><\/h2>\n<p>In this article, we looked at a use case where logpoints offer a simpler and more elegant alternative to breakpoints or println logging. We:<\/p>\n<ul>\n<li>Attached to a remote process<\/li>\n<li>Added diagnostics at runtime using logpoints<\/li>\n<li>Adjusted logging on the fly<\/li>\n<li>Tested the fix without changing the application code<\/li>\n<li>Changed the timeout for a running server<\/li>\n<li>Used the <kbd>ij-debugger<\/kbd> AI agent skill<\/li>\n<\/ul>\n<p>I hope you learned something new and now have a better option for the next time println statements or breakpoints get in the way. In the next post of the series, we\u2019ll look at debugger instrumentation, the underlying mechanism that makes the new logpoints so fast.<\/p>\n<p>Happy debugging!<\/p>\n<\/p><\/div>\n<p> <a href=\"#\"><\/a> <\/section>\n<div>\n<p><h2>Discover more<\/h2>\n<\/p><\/div>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/blog.jetbrains.com\/idea\/2026\/09\/logpoints-walkthrough\/\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>IntelliJ IDEA Modern development tools, especially IntelliJ IDEA, have such comprehensive debugging support that for virtually any niche use case, there is a specialized tool for the job. This can make it hard to know where to begin. If you are new to debugging tools and want the biggest return on your learning investment, the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":5316,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"webixso_pending_account_ids":""},"categories":[46],"tags":[],"class_list":["post-5317","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-jetbrain"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/5317","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=5317"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/5317\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/5316"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=5317"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=5317"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=5317"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}