1"use strict";(globalThis.webpackChunkpassbolt_docs||=[]).push([[2033],{78617(e,n,s){s.r(n),s.d(n,{assets:()=>p,contentTitle:()=>h,default:()=>b,frontMatter:()=>d,metadata:()=>t,toc:()=>g});const t=JSON.parse('{"id":"hosting/maintenance/performance/purge-action-logs","title":"How to purge the action logs","description":"A dedicated guide that explains how to purge the action logs from the API","source":"@site/docs/hosting/maintenance/performance/purge-action-logs.mdx","sourceDirName":"hosting/maintenance/performance","slug":"/hosting/maintenance/performance/purge-action-logs","permalink":"/docs/hosting/maintenance/performance/purge-action-logs","draft":false,"unlisted":false,"editUrl":"https://github.com/passbolt/passbolt-docs/blob/main/docs/hosting/maintenance/performance/purge-action-logs.mdx","tags":[],"version":"current","lastUpdatedAt":1785892562000,"frontMatter":{"title":"How to purge the action logs","sidebar_label":"Purge Action Logs","description":"A dedicated guide that explains how to purge the action logs from the API","hide_table_of_contents":false},"sidebar":"hostingGuideSidebar","previous":{"title":"Performance tweaks","permalink":"/docs/hosting/maintenance/performance/performance-tweaks"},"next":{"title":"Routine maintenance","permalink":"/docs/hosting/maintenance/performance/routine-maintenance"}}');var a=s(74848),r=s(28453),o=s(42987),i=s(25515),l=s(37871),c=s(4865),u=s(19365);const d={title:"How to purge the action logs",sidebar_label:"Purge Action Logs",description:"A dedicated guide that explains how to purge the action logs from the API",hide_table_of_contents:!1},h=void 0,p={},g=[{value:"Understanding Action Logs and Database Impact",id:"understanding-action-logs-and-database-impact",level:2},{value:"Command Options",id:"command-options",level:2},{value:"Dry Run (Recommended First Step)",id:"dry-run-recommended-first-step",level:2},{value:"Purging Action Logs",id:"purging-action-logs",level:2},{value:"Additional Examples",id:"additional-examples",level:2},{value:"Verbose Output",id:"verbose-output",level:3},{value:"Custom Batch Size",id:"custom-batch-size",level:3},{value:"Regular Maintenance",id:"regular-maintenance",level:2}];function m(e){const n={a:"a",admonition:"admonition",code:"code",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{style:{display:"flex",gap:"1rem"},children:[(0,a.jsx)(i.A,{link:"https://www.passbolt.com/ce/docker",isUnderMainTitle:!0,children:"CE"}),(0,a.jsx)(i.A,{link:"https://www.passbolt.com/pricing/pro",isUnderMainTitle:!0,children:"Pro"})]}),"\n",(0,a.jsxs)(n.p,{children:["As your instance ages, you will want to reduce the size of the ",(0,a.jsx)(n.code,{children:"action_logs"})," table by dropping old entries persisted in the database.\nAt least the ones that do not log sensitive actions. Passbolt provides a command to do so.\nThis will enhance the performance of your instance, without compromise on security, user or password history."]}),"\n",(0,a.jsx)(n.h2,{id:"understanding-action-logs-and-database-impact",children:"Understanding Action Logs and Database Impact"}),"\n",(0,a.jsxs)(n.p,{children:["The ",(0,a.jsx)(n.code,{children:"action_logs"})," table in your Passbolt database stores audit trail information about user actions within the system. Over time, this table can grow significantly, potentially impacting database performance and storage requirements. Regular maintenance through purging old logs helps maintain optimal instance performance."]}),"\n",(0,a.jsxs)(n.p,{children:["The purge command processes logs in batches to avoid overwhelming the database. By default, it processes up to 100,000 log entries per run. If you have more logs to purge than the batch limit, you may need to run the command multiple times or adjust the batch size using the ",(0,a.jsx)(n.code,{children:"--limit"})," option."]}),"\n",(0,a.jsx)(n.admonition,{title:"Schedule a Maintenance Window",type:"tip",children:(0,a.jsxs)(n.p,{children:["For older instances with large ",(0,a.jsx)(n.code,{children:"action_logs"})," tables, we recommend scheduling a maintenance window for the initial purge operation. During the purge, database performance may be temporarily affected, which could impact user experience. Plan your first purge during a low-traffic period."]})}),"\n",(0,a.jsx)(n.h2,{id:"command-options",children:"Command Options"}),"\n",(0,a.jsxs)(n.p,{children:["The ",(0,a.jsx)(n.code,{children:"action_logs_purge"})," command supports the following options:"]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"-r, --retention-in-days"})," - Retention period in days (required, must be > 0). Action logs older than this period will be irrevocably purged."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"-l, --limit"})," - Maximum number of rows to purge per run (default: 100000). This limits how many log entries are processed in a single run. For large datasets, you may
1need to run the command multiple times or adjust this value."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"-d, --dry-run"})," - Dry run mode. Preview without deleting. Shows the number of entries that would be purged without actually removing them. Always recommended for first run."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"-v, --verbose"})," - Display the count of logs grouped by actions before and after the purge."]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"-q, --quiet"})," - Enable quiet output. Suppresses normal output messages."]}),"\n"]}),"\n",(0,a.jsx)(n.admonition,{type:"warning",children:(0,a.jsxs)(n.p,{children:["Performance may be degraded while the purge command is running. Always run with ",(0,a.jsx)(n.code,{children:"--dry-run"})," first to preview the impact."]})}),"\n",(0,a.jsx)(n.h2,{id:"dry-run-recommended-first-step",children:"Dry Run (Recommended First Step)"}),"\n",(0,a.jsxs)(n.p,{children:["Before purging logs, always run the command with ",(0,a.jsx)(n.code,{children:"--dry-run"})," to see how many entries would be removed:"]}),"\n",(0,a.jsxs)(c.A,{groupId:"installation",children:[(0,a.jsx)(u.A,{value:"package",label:"Package Installation",children:(0,a.jsx)(l.A,{children:'sudo su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt action_logs_purge --dry-run -r 90" www-data'})}),(0,a.jsx)(u.A,{value:"source",label:"From Source",children:(0,a.jsx)(l.A,{children:'sudo su -s /bin/bash -c "/var/www/passbolt/bin/cake passbolt action_logs_purge --dry-run -r 90" www-data'})}),(0,a.jsx)(u.A,{value:"docker",label:"Docker",children:(0,a.jsx)(l.A,{children:'docker compose exec passbolt su -s /bin/bash -c "./bin/cake passbolt action_logs_purge --dry-run -r 90" www-data'})})]}),"\n",(0,a.jsx)(o.A,{src:"/img/help/2024/05/purge-action-logs-command-dry-run.png",caption:"fig. Purge Action Logs Command Dry Run",alt:"Purge Action Logs Command Dry Run"}),"\n",(0,a.jsx)(n.h2,{id:"purging-action-logs",children:"Purging Action Logs"}),"\n",(0,a.jsx)(n.p,{children:"Once you've verified the dry-run output, you can proceed with the actual purge. The following example purges logs older than 90 days:"}),"\n",(0,a.jsx)(n.admonition,{title:"Out of Memory Risk",type:"warning",children:(0,a.jsxs)(n.p,{children:["When purging large datasets, the command may consume significant memory. If you encounter Out of Memory (OOM) errors, reduce the batch size using the ",(0,a.jsx)(n.code,{children:"--limit"})," option. For example, use ",(0,a.jsx)(n.code,{children:"-l 10000"})," or ",(0,a.jsx)(n.code,{children:"-l 25000"})," to process smaller batches. This is especially important on systems with limited RAM or when purging millions of log entries."]})}),"\n",(0,a.jsxs)(c.A,{groupId:"installation",children:[(0,a.jsx)(u.A,{value:"package",label:"Package Installation",children:(0,a.jsx)(l.A,{children:'sudo su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt action_logs_purge -r 90" www-data'})}),(0,a.jsx)(u.A,{value:"source",label:"From Source",children:(0,a.jsx)(l.A,{children:'sudo su -s /bin/bash -c "/var/www/passbolt/bin/cake passbolt action_logs_purge -r 90" www-data'})}),(0,a.jsx)(u.A,{value:"docker",label:"Docker",children:(0,a.jsx)(l.A,{children:'docker compose exec passbolt su -s /bin/bash -c "./bin/cake passbolt action_logs_purge -r 90" www-data'})})]}),"\n",(0,a.jsx)(o.A,{src:"/img/help/2024/05/purge-action-logs-command.png",caption:"fig. Purge Action Logs Command",alt:"Purge Action Logs Command"}),"\n",(0,a.jsx)(n.h2,{id:"additional-examples",children:"Additional Examples"}),"\n",(0,a.jsx)(n.h3,{id:"verbose-output",children:"Verbose Output"}),"\n",(0,a.jsx)(n.p,{children:"To see detailed information about logs grouped by action type:"}),"\n",(0,a.jsxs)(c.A,{groupId:"installation",children:[(0,a.jsx)(u.A,{value:"package",label:"Package Installation",children:(0,a.jsx)(l.A,{children:'sudo su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt action_logs_purge -r 90 -v" www-data'})}),(0,a.jsx)(u.A,{value:"source",label:"From Source",children:(0,a.jsx)(l.A,{children:'sudo su -s /bin/bash -c "/var/www/passbolt/bin/cake passbolt action_logs_purge -r 90 -v" www-data'})}),(0,a.jsx)(u.A,{value:"docker",label:"Docker",children:(0,a.jsx)(l.A,{children:'docker
1compose exec passbolt su -s /bin/bash -c "./bin/cake passbolt action_logs_purge -r 90 -v" www-data'})})]}),"\n",(0,a.jsx)(n.h3,{id:"custom-batch-size",children:"Custom Batch Size"}),"\n",(0,a.jsx)(n.p,{children:"For large datasets, you may want to adjust the batch size. If you have more than 100,000 logs to purge, you can either:"}),"\n",(0,a.jsxs)(n.ol,{children:["\n",(0,a.jsx)(n.li,{children:"Run the command multiple times (it will continue processing until all eligible logs are purged)"}),"\n",(0,a.jsxs)(n.li,{children:["Adjust the batch size using the ",(0,a.jsx)(n.code,{children:"--limit"})," option:"]}),"\n"]}),"\n",(0,a.jsxs)(c.A,{groupId:"installation",children:[(0,a.jsx)(u.A,{value:"package",label:"Package Installation",children:(0,a.jsx)(l.A,{children:'# Process 50,000 logs per batch (useful for systems with limited resources)\nsudo su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt action_logs_purge -r 90 -l 50000" www-data\n\n# Or increase to 200,000 for faster processing on systems with sufficient resources\nsudo su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt action_logs_purge -r 90 -l 200000" www-data'})}),(0,a.jsx)(u.A,{value:"source",label:"From Source",children:(0,a.jsx)(l.A,{children:'# Process 50,000 logs per batch (useful for systems with limited resources)\nsudo su -s /bin/bash -c "/var/www/passbolt/bin/cake passbolt action_logs_purge -r 90 -l 50000" www-data\n\n# Or increase to 200,000 for faster processing on systems with sufficient resources\nsudo su -s /bin/bash -c "/var/www/passbolt/bin/cake passbolt action_logs_purge -r 90 -l 200000" www-data'})}),(0,a.jsx)(u.A,{value:"docker",label:"Docker",children:(0,a.jsx)(l.A,{children:'# Process 50,000 logs per batch (useful for systems with limited resources)\ndocker compose exec passbolt su -s /bin/bash -c "./bin/cake passbolt action_logs_purge -r 90 -l 50000" www-data\n\n# Or increase to 200,000 for faster processing on systems with sufficient resources\ndocker compose exec passbolt su -s /bin/bash -c "./bin/cake passbolt action_logs_purge -r 90 -l 200000" www-data'})})]}),"\n",(0,a.jsx)(n.admonition,{type:"info",children:(0,a.jsx)(n.p,{children:"The command processes logs in batches. If you have more logs to purge than the batch limit, the command will continue processing in subsequent runs until all eligible logs are removed. This makes it safe to schedule via cron for regular maintenance."})}),"\n",(0,a.jsx)(n.h2,{id:"regular-maintenance",children:"Regular Maintenance"}),"\n",(0,a.jsxs)(n.p,{children:["For ongoing maintenance, consider scheduling the purge command via cron. This ensures your ",(0,a.jsx)(n.code,{children:"action_logs"})," table remains at a manageable size. Example cron job to purge logs older than 90 days monthly:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-bash",children:'# Add to crontab (crontab -e)\n# Run on the first day of each month at 2 AM\n0 2 1 * * sudo su -s /bin/bash -c "/usr/share/php/passbolt/bin/cake passbolt action_logs_purge -r 90" www-data\n'})}),"\n",(0,a.jsxs)(n.p,{children:["Adjust the retention period (",(0,a.jsx)(n.code,{children:"-r"}),") based on your organisation's audit and compliance requirements. Common retention periods range from 30 to 365 days depending on regulatory needs."]}),"\n",(0,a.jsx)(n.admonition,{title:"Shipping logs elsewhere changes the retention question",type:"tip",children:(0,a.jsxs)(n.p,{children:["If you forward action logs to a SIEM, the audit history is retained outside passbolt, so the database only needs to hold what your instance itself requires. That usually allows a shorter retention period here. Confirm your SIEM is ingesting successfully before you shorten it, because purging is irreversible and does not re-emit anything. See ",(0,a.jsx)(n.a,{href:"/hosting/maintenance/diagnostics/forward-action-logs/",children:"forwarding action logs to a SIEM"}),"."]})})]})}function b(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(m,{...e})}):m(e)}},19365(e,n,s){s.d(n,{A:()=>l});s(96540);var t=s(18215),a=s(47751);const r="tabItem_Ymn6";var o=s(74848);function i({children:e,className:n,hidden:s}){return(0,o.jsx)("div",{role:"tabpanel",className:(0,t.A)(r,n),hidden:s,children:e})}function l({children:e,className:n,value:s}){const{selectedValue:t,lazy:r}=(0,a.uc)(),l=s===t;return!l&&r?null:(0,o.jsx)(i,{className:n,hidden:!l,children:e})}},4865(e,n,s){s.d(n,{A:()=>g});s(96540);var t=s(18215),a=s(17559),r=s(47751),o=s(23104),i=s(92303);const l="tabList__CuJ",c="tabItem_LNqP";var u=s(74848);function d({className:e}){const{selectedValue:n,selectValue:s,tabValues:a,block:i}=(0,r.uc)(),l=[],{blockElementScrollPositionUntilNextRender:d}
1=(0,o.a_)(),h=e=>{const t=e.currentTarget,r=l.indexOf(t),o=a[r].value;o!==n&&(d(t),s(o))},p=e=>{let n=null;switch(e.key){case"Enter":h(e);break;case"ArrowRight":{const s=l.indexOf(e.currentTarget)+1;n=l[s]??l[0];break}case"ArrowLeft":{const s=l.indexOf(e.currentTarget)-1;n=l[s]??l[l.length-1];break}}n?.focus()};return(0,u.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,t.A)("tabs",{"tabs--block":i},e),children:a.map(({value:e,label:s,attributes:a})=>(0,u.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{l.push(e)},onKeyDown:p,onClick:h,...a,className:(0,t.A)("tabs__item",c,a?.className,{"tabs__item--active":n===e}),children:s??e},e))})}function h({children:e}){return(0,u.jsx)("div",{className:"margin-top--md",children:e})}function p({className:e,children:n}){return(0,u.jsxs)("div",{className:(0,t.A)(a.G.tabs.container,"tabs-container",l),children:[(0,u.jsx)(d,{className:e}),(0,u.jsx)(h,{children:n})]})}function g(e){const n=(0,i.A)(),s=(0,r.OC)(e);return(0,u.jsx)(r.O_,{value:s,children:(0,u.jsx)(p,{className:e.className,children:(0,r.vT)(e.children)})},String(n))}},47751(e,n,s){s.d(n,{OC:()=>g,O_:()=>f,uc:()=>b,vT:()=>u});var t=s(96540),a=s(56347),r=s(205),o=s(57485),i=s(70679),l=s(31682),c=s(74848);function u(e){return t.Children.toArray(e).filter(e=>"\n"!==e)}function d(e){const{values:n,children:s}=e;return(0,t.useMemo)(()=>{const e=n??function(e){return t.Children.toArray(e).flatMap(e=>{if(!e)return[];if((0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];const n="string"==typeof e.type?e.type:e.type.name;throw new Error(`Docusaurus error: Bad <Tabs> child <${n}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.\nIf you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)}).map(({props:{value:e,label:n,attributes:s,default:t}})=>({value:e,label:n,attributes:s,default:t}))}(s);return function(e){const n=(0,l.XI)(e,(e,n)=>e.value===n.value);if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map(e=>`'${e.value}'`).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e},[n,s])}function h({value:e,tabValues:n}){return n.some(n=>n.value===e)}function p({queryString:e=!1,groupId:n}){const s=(0,a.W6)(),r=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,o.aZ)(r),(0,t.useCallback)(e=>{if(!r)return;const n=new URLSearchParams(s.location.search);n.set(r,e),s.replace({...s.location,search:n.toString()})},[r,s])]}function g(e){const{defaultValue:n,queryString:s=!1,groupId:a}=e,o=d(e),[l,c]=(0,t.useState)(()=>function({defaultValue:e,tabValues:n}){if(0===n.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!h({value:e,tabValues:n}))throw new Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const s=n.find(e=>e.default)??n[0];if(!s)throw new Error("Unexpected error: 0 tabValues");return s.value}({defaultValue:n,tabValues:o})),[u,g]=p({queryString:s,groupId:a}),[m,b]=function({groupId:e}){const n=function(e){return e?`docusaurus.tab.${e}`:null}(e),[s,a]=(0,i.Dv)(n);return[s,(0,t.useCallback)(e=>{n&&a.set(e)},[n,a])]}({groupId:a}),f=(()=>{const e=u??m;return h({value:e,tabValues:o})?e:null})();(0,r.A)(()=>{f&&c(f)},[f]);return{selectedValue:l,selectValue:(0,t.useCallback)(e=>{if(!h({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);c(e),g(e),b(e)},[g,b,o]),tabValues:o,lazy:e.lazy??!1,block:e.block??!1}}const m=(0,t.createContext)(null);function b(){const e=t.useContext(m);if(!e)throw new Error("useTabsContext() must be used within a Tabs component");return e}function f(e){return(0,c.jsx)(m.Provider,{value:e.value,children:e.children})}},25515(e,n,s){s.d(n,{A:()=>d});var t=s(28774),a=s(5556),r=s.n(a);const o="chips_cpy_",i="link_WvDp",l="under-main-menu_SavF";var c=s(74848);const u=({children:e,link:n="",isUnderMainTitle:s=!1})=>{if(!e)return null;const a=n.length>0;return(0,c.jsx)("div",{className:`${s?l:""}`,children:a?(0,c.jsx)(t.A,{className:`${o} ${i}`,to:n,target:"_blank",children:e}):(0,c.jsx)("span",{className:o,children:e})})};u.propTypes={children:r().oneOfType([r().arrayOf(r().node),r().node]).isRequired,isUnderMainTitle:r().bool,link:r().string};const d=u},37871(e,n,s){var t=s(83457),a=s(5556),r=s.n(a),o=s(74848);const i=({children:e})=>e?(0,o.jsx)(t.A,{className:"bash",children:e}):null;i.propTypes={children:r().oneOfType([r().arrayOf(r().node),r().node]).isRequired};const l=i;s.d(n,["A",0,l])},42987(e,n,s){s.d(n,{A:()=>c});var t=s(86025),a=s(5556),r=s.n(a);const o="root_Qk78";var i=s(74848);const l=({src:e,alt:n,caption:s=null,size:a={}})=>{const r=(0,t.Ay)(e),l=a.width||a.height?{width:a.width,height:a.height}:{};return(0,i.jsxs)("figure",{className:o,children:[(0,i.jsx)("img",{src:r,alt:n,style:l}),s&&(0,i.jsx)("figcaption",{children:s})]})};
1l.propTypes={src:r().string.isRequired,alt:r().string.isRequired,caption:r().string,size:r().shape({width:r().oneOfType([r().string,r().number]),height:r().oneOfType([r().string,r().number])})};const c=l}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.