{"id":155773,"date":"2021-03-25T23:30:17","date_gmt":"2021-03-26T06:30:17","guid":{"rendered":"https:\/\/dreness.com\/blog\/?p=155773"},"modified":"2021-06-07T01:38:14","modified_gmt":"2021-06-07T08:38:14","slug":"os_log-haystack-needler","status":"publish","type":"post","link":"https:\/\/dreness.com\/blog\/archives\/155773","title":{"rendered":"os_log haystack needler"},"content":{"rendered":"\n<p>Here&#8217;s a method to find where any given NSUserDefault setting is persisted. This might help if you&#8217;re trying to write a script to get or set that setting. As a bonus, this post details a reusable strategy for withstanding the cacophonous onslaught of os_log long enough to find what you came for, or convince yourself it&#8217;s not there.<\/p>\n\n\n\n<p>For this example, let&#8217;s say you&#8217;re trying to find where macOS stores the hotkey for invoking Spotlight. The UI for this is System Preferences &#8211;&gt; Keyboard &#8211;&gt; Shortcuts &#8211;&gt; Spotlight, and the setting is called &#8216;Show Spotlight search&#8217;.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1024x929.png\" alt=\"\" class=\"wp-image-155774\" width=\"512\" height=\"465\" srcset=\"https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1024x929.png 1024w, https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-300x272.png 300w, https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1536x1394.png 1536w, https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image.png 1560w\" sizes=\"auto, (max-width: 512px) 100vw, 512px\" \/><\/a><figcaption>The Shortcuts tab of the Keyboard pane of System Preferences with Spotlight selected<\/figcaption><\/figure>\n\n\n\n<p>We know that macOS <a href=\"https:\/\/developer.apple.com\/documentation\/os\/logging\/viewing_log_messages?language=occ\">logs<\/a> quite a bit &#8211; so much, in fact, that the hard part about using logs is achiving a favorable signal to noise ratio. It&#8217;s usually much easier to select signal than it is to filter noise &#8211; especially if you already know something about what you&#8217;re looking for. For this case study we can get an initial lead, but even if we couldn&#8217;t, our strategy will still work, maybe with an extra iteration or two.<\/p>\n\n\n\n<p>The <code>log<\/code> man page tells us we can match log events in lots of ways, including by process &#8211; but which process should we look upon?<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>andre@boop ~ % apropos prefs\nCPAN::Distroprefs(3)     - -- read and match distroprefs\ncfprefsd(8)              - defaults server<\/code><\/pre>\n\n\n\n<p>If I didn&#8217;t already know that <code>cfprefsd<\/code> is our target, I would read the <code>cfprefsd<\/code> man page (because all <code>apropos<\/code> results are man pages). We could start by listening to all of <code>cfprefsd<\/code> by running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>log stream --debug --predicate 'process == \"cfprefsd\"'<\/code><\/pre>\n\n\n\n<p>\u2026 but that output is not so easy to read. Let&#8217;s use <code>log<\/code>&#8216;s <code>ndjson<\/code> output style so we can view it with <code>jq<\/code>, but first let&#8217;s make sure the json output is clean enough (foreshadowing!)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>andre@boop ~ % log stream --predicate 'process == \"cfprefsd\"' --style ndjson --debug                                  \nFiltering the log data using \"process == \"cfprefsd\"\"\n{\"traceID\":36107480853643780,\"eventMessage\":\"Process 68 (UserEventAgent)\n...<\/code><\/pre>\n\n\n\n<p>Nope, <code>jq<\/code> won&#8217;t like that, so let&#8217;s filter the <code>log<\/code> output with <code>egrep --line-buffered '^{'<\/code> to only pass lines starting with <code>{<\/code> to <code>jq<\/code>. Don&#8217;t forget <code>--line-buffered<\/code>, otherwise <code>grep<\/code> &#8216;helps&#8217; by saving a tiny fraction of computer by showing output less often, which <code>grep<\/code> believes is appropriate because it doesn&#8217;t think there&#8217;s a human watching (because its stdout isn&#8217;t connected to a terminal, it is connected to <code>jq<\/code>).<\/p>\n\n\n\n<p>Before we continue, as our <code>log<\/code> command is becomming lengthy, let&#8217;s clean up a little by setting some common options in <code>~\/.logrc<\/code> (described in the <code>log<\/code> man page). Here&#8217;s a command to populate <code>~\/.logrc<\/code> with some settings useful for the task at hand:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &gt; ~\/.logrc &lt;&lt;EOF\nstream:\n   --debug\n   --style ndjson\n\npredicate:\n   prefs 'process contains \"cfprefsd\"'\nEOF<\/code><\/pre>\n\n\n\n<p>Now when we run <code>log stream --predicate prefs<\/code>, <code>log<\/code> behaves as though we typed:<\/p>\n\n\n\n<p><code>log stream --debug --style ndjson --predicate 'process CONTAINS \"cfprefsd\"<\/code><\/p>\n\n\n\n<p>Ok, putting it all together:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>andre@boop ~ % log stream --predicate prefs | egrep --line-buffered '^{' | jq                    \n{\n  \"traceID\": 36107480853643780,\n  \"eventMessage\": \"Process 1347 (ContextStoreAgent) sent a request related to { ContextStoreAgent, user: andre, kCFPreferencesAnyHost, \/Users\/andre\/Library\/Preferences\/ContextStoreAgent.plist, managed: 0 } (0x7fe2c19057d0)\",\n  \"eventType\": \"logEvent\",\n  \"source\": null,\n  \"formatString\": \"Process %{public}d (%{public}s) sent a request related to { %{public}s, user: %{public}s, %{public}s, %{public}s, managed: %d } (%p)\",\n  \"activityIdentifier\": 7609380,\n  \"subsystem\": \"com.apple.defaults\",\n  \"category\": \"cfprefsd\",\n  \"threadID\": 5781976,\n  \"senderImageUUID\": \"2A0E160E-9EE6-3B23-8832-6979A16EC250\",\n  \"backtrace\": {\n    \"frames\": &#91;\n      {\n        \"imageOffset\": 1698619,\n        \"imageUUID\": \"2A0E160E-9EE6-3B23-8832-6979A16EC250\"\n      }\n    ]\n  },\n  \"bootUUID\": \"\",\n  \"processImagePath\": \"\/usr\/sbin\/cfprefsd\",\n  \"timestamp\": \"2021-03-20 18:30:44.017031-0700\",\n  \"senderImagePath\": \"\/System\/Library\/Frameworks\/CoreFoundation.framework\/Versions\/A\/CoreFoundation\",\n  \"machTimestamp\": 585248221966448,\n  \"messageType\": \"Debug\",\n  \"processImageUUID\": \"5E5EBAAF-AA0E-38E1-B19A-07FB2FB8ECED\",\n  \"processID\": 406,\n  \"senderProgramCounter\": 1698619,\n  \"parentActivityIdentifier\": 0,\n  \"timezoneName\": \"\"\n}\n...<\/code><\/pre>\n\n\n\n<p>Definitely more legible, but\u2026 the above represents a single log event. We can do better. <code>eventMessage<\/code> has the interesting info, so let&#8217;s look only at that by using a <code>jq<\/code> filter: <code>'. | .eventMessage'<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>andre@boop ~ % log stream --predicate prefs \\\n| egrep --line-buffered '^{' | jq '. | .eventMessage'\n\"Process 1347 (ContextStoreAgent) sent a request related to { ContextStoreAgent, user: andre, kCFPreferencesAnyHost, \/Users\/andre\/Library\/Preferences\/ContextStoreAgent.plist, managed: 0 } (0x7fe2c19057d0)\"\n\"Process 1347 (ContextStoreAgent) sent a request related to { ContextStoreAgent, user: andre, kCFPreferencesAnyHost, \/Users\/andre\/Library\/Preferences\/ContextStoreAgent.plist, managed: 0 } (0x7fe2c19057d0)\"\n\"Process 97346 (PerfPowerServices) sent a request related to { com.apple.powerlogd, user: kCFPreferencesAnyUser, kCFPreferencesCurrentHost, \/Library\/Preferences\/com.apple.powerlogd.plist, managed: 0 } (0x7fb3ee304a30)\"<\/code><\/pre>\n\n\n\n<p>In this case that&#8217;s probably close enough, and you&#8217;ll probably see stuff about keys being written. Let&#8217;s pretend it&#8217;s still too noisy. What if we had a way to see only the different <em>kinds<\/em> of messages without the dynamic parts of each. We do! Just ask for <code>formatString<\/code> instead of <code>eventMessage<\/code>, then <code>sort | uniq<\/code>.<\/p>\n\n\n\n<p>There is a complication, though. If you append <code>sort | uniq<\/code> to the previous pipeline and run it, you will see no output &#8211; this is because both <code>sort<\/code> and <code>unique<\/code> can&#8217;t run in stream mode; they have to receive all the input before they can do their job. If you simply control-c to terminate <code>log<\/code>, this seems to cause the in-flight log data to become malformed some time before it reaches <code>jq<\/code>. Instead of figuring that out, I opted to forgo <code>log stream<\/code> in favor of <code>log show<\/code>, because <code>log show<\/code> doesn&#8217;t run forever, so I won&#8217;t have to control-c it and have broken pipes.<\/p>\n\n\n\n<p>There&#8217;s still one more problem (this is the last one, honest). Let&#8217;s have a look at the <code>logd<\/code> man page:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>     All processes that use os_log(3) share pages of memory with logd and\n     append entries to them.  logd reads pages and combines and compresses\n     this data.  Depending on configured policies, it keeps them in a local\n     ring buffer or writes them out to persistent storage.\n\n           { logd }\n              | `---&gt; &#91; buffer ] ----.\n              |                      ,`---&gt; { log show }\n              `---&gt; &#91; data store ] -'<\/code><\/pre>\n\n\n\n<p>You may have noticed <code>\"messageType\": \"Debug\",<\/code> in the previous output, indicating that the messages of interest are logged at the debug level. When you pass the <code>--debug<\/code> option to <code>log stream<\/code>, <code>log<\/code> temporarily activates debug-level messages so you can see them as they happen, and then disables debug mode when you control-c. In other words, switching to <code>log show<\/code> won&#8217;t work until we enable debug-level messages some other way. There are at least two ways to do this according to the <code>log<\/code> man page:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># change the global log level to debug\nsudo log config --mode \"level: debug\"\n\n# ... or set a specific subsystem to debug\nsudo log config --subsystem 'com.apple.defaults' --mode level:debug --mode persist:debug<\/code><\/pre>\n\n\n\n<p>I don&#8217;t like the first method because it doesn&#8217;t stick &#8211; the next time a <code>log stream<\/code> command exits, <code>log<\/code> helpfully disables debug-level messages, even if they were enabled before running <code>log stream<\/code>.<\/p>\n\n\n\n<p>Note that when targeting a subsystem, we also need to enable <em>persisting<\/em> of the debug-level messages, otherwise those messages will briefly pass through the in-memory ring buffer before being forgotten.<\/p>\n\n\n\n<p>We can now finally achieve a good summary of log activity by simply counting the unique instances of <code>formatString<\/code> values.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>andre@boop % log show --last 10m --predicate prefs \\\n| egrep --line-buffered '^{' | jq '. | .formatString' &gt; fstrings\n\nandre@boop % cat fstrings | sort | uniq -c | sort -n\n   1 null\n  32 \"Data for { %@, %@ } was purged due to memory pressure\"\n  32 \"no cache for { %{public}s, %{public}s, %{public}s, %{public}s, managed: %d }, loading from %{public}s\"\n  38 \"wrote file %{public}s\"\n  42 \"Notifying observers of { %{public}s, %{public}s, %{public}s, %{public}s, managed: %d }\"\n  82 \"Process %{public}d (%{public}s) wrote the key(s) %{public}s in { %{public}s, %{public}s, %{public}s, %{public}s, managed: %d }\"\n  90 \"Process %{public}d (%{public}s) read data for { %{public}s, %{public}s, %{public}s, %{public}s, managed: %d }, backed by %{public}s\"\n 416 \"Process %{public}d (%{public}s) sent a request related to { %{public}s, user: %{public}s, %{public}s, %{public}s, managed: %d } (%p)\"\n 482 \"Couldn't %{public}s %{private}s due to %{public}s\"<\/code><\/pre>\n\n\n\n<p>Ok, <code>wrote file ...<\/code> and <code>process ... wrote the key(s) ...<\/code> look promising for this sample case. Let&#8217;s update our <code>.logrc<\/code> file to add a stanza for <code>show<\/code>. Also change the <code>prefs<\/code> predicate macro to specify <code>category = \"cfprefsd\"<\/code> instead of <code>process<\/code> (not strictly required, but good to use categories when possible). Lastly, add another predicate macro called <code>keys<\/code> to conjure a refined version of the <code>prefs<\/code> search that will only return events that represent writes to NSUserDefaults.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>show:\n   --style compact\n   --color always\n   --last 1h\n   --debug\n   --style ndjson\n\nstream:\n   --debug\n   --style ndjson\n\npredicate:\n   prefs 'category = \"cfprefsd\"' \n   keys 'category = \"cfprefsd\" and eventMessage contains \"wrote\"'<\/code><\/pre>\n\n\n\n<p>Now we just use the <code>keys<\/code> predicate macro and clean the output with <code>egrep<\/code> and <code>jq<\/code> like before:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>andre@boop ~ % log show --last 10m --predicate keys \\\n  | egrep --line-buffered '^{' | jq '. | .eventMessage'\n...\n\"wrote file \/Users\/andre\/Library\/Preferences\/com.apple.symbolichotkeys.plist\"\n...<\/code><\/pre>\n\n\n\n<p>If you came to learn how to claw law blogs from os_log&#8217;s sprawling maw, get on outta here Bob Loblaw &#8211; the rest is about hotkeys.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p>There are at least two different representations of hotkeys you might encounter in NSUserDefaults. <code>NSUserKeyEquivalents<\/code> (as in <code>defaults find NSUserKeyEquivalents<\/code>) is pretty self-explanatory, but there&#8217;s also <code>AppleSymbolicHotKeys<\/code> which is somewhat more opaque.<\/p>\n\n\n\n<p>As an example of <code>AppleSymbolicHotKeys<\/code>, run<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>defaults find AppleSymbolicHotKeys &gt; hotkey1.plist<\/code><\/pre>\n\n\n\n<p>\u2026 then change the spotlight hotkey in system preferences, then run<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>defaults find AppleSymbolicHotKeys &gt; hotkey2.plist<\/code><\/pre>\n\n\n\n<p>\u2026 then diff these plists:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>***************\n*** 460,474 ****\n              };\n          };\n          64 =         {\n              enabled = 1;\n              value =             {\n                  parameters =                 (\n!                     32,\n!                     49,\n!                     1048576\n                  );\n                  type = standard;\n              };\n          };\n          65 =         {\n              enabled = 1;\n--- 460,474 ----\n              };\n          };\n          64 =         {\n              enabled = 1;\n              value =             {\n                  parameters =                 (\n!                     107,\n!                     40,\n!                     786432\n                  );\n                  type = standard;\n              };\n          };\n          65 =         {\n              enabled = 1;<\/code><\/pre>\n\n\n\n<p>Looking at the whole file, it seems like maybe the different functions that can be assigned hotkeys are represented here by a numeric ID, and <code>64<\/code> is the one for &#8220;show spotlight search&#8221;. The parameter values are key codes.<\/p>\n\n\n\n<p>I found a neat app called <a href=\"https:\/\/manytricks.com\/keycodes\/\">Key Codes<\/a> that can be used to understand how the key code values work. For example, if I:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>disable the spotlight command-space hotkey so that Key Codes can capture that hotkey<\/li><li>click the little &#8216;i&#8217; button adjacent to &#8216;Modifiers&#8217;<\/li><li>compare that to the data we found from NSUserDefaults<\/li><\/ul>\n\n\n\n<p>\u2026 we see that the codes for command-space are <code>32, 49, 1048576<\/code> &#8211; note especially that the stored value does not include the device-specific aspect of the modifier (<code>0x8<\/code>). This matches what is shown in the diff output above from <code>hotkey1.plist<\/code><\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1-1024x757.png\" alt=\"\" class=\"wp-image-155775\" width=\"512\" height=\"379\" srcset=\"https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1-1024x757.png 1024w, https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1-300x222.png 300w, https:\/\/dreness.com\/blog\/wp-content\/uploads\/2021\/03\/image-1.png 1506w\" sizes=\"auto, (max-width: 512px) 100vw, 512px\" \/><\/a><figcaption>Screenshot of the app Key Codes displaying key code information about the hotkey &#8216;?-space&#8217;<\/figcaption><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>Here&#8217;s a method to find where any given NSUserDefault setting is persisted. This might help if you&#8217;re trying to write a script to get or set that setting. As a bonus, this post details a reusable strategy for withstanding the &hellip; <a href=\"https:\/\/dreness.com\/blog\/archives\/155773\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,13,10],"tags":[],"class_list":["post-155773","post","type-post","status-publish","format-standard","hentry","category-os-x","category-the-more-you-know","category-tutorials"],"_links":{"self":[{"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/posts\/155773","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/comments?post=155773"}],"version-history":[{"count":5,"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/posts\/155773\/revisions"}],"predecessor-version":[{"id":180946,"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/posts\/155773\/revisions\/180946"}],"wp:attachment":[{"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/media?parent=155773"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/categories?post=155773"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dreness.com\/blog\/wp-json\/wp\/v2\/tags?post=155773"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}