UNPKG

deadslog

Version:

A dead simple logger module for Node.js

3 lines (2 loc) 9.15 kB
import Be from"node:tty";var O,ee,j,te,Ee=((te=(j=(ee=(O=Be)==null?void 0:O.WriteStream)==null?void 0:ee.prototype)==null?void 0:j.hasColors)==null?void 0:te.call(j))??!1,r=(s,t)=>{if(!Ee)return m=>m;let L=`\x1B[${s}m`,f=`\x1B[${t}m`;return m=>{let h=m+"",b=h.indexOf(f);if(b===-1)return L+h+f;let z=L,p=0;for(;b!==-1;)z+=h.slice(p,b)+L,p=b+f.length,b=h.indexOf(f,p);return z+=h.slice(p)+f,z}},Ie=r(0,0),De=r(1,22),He=r(2,22),Ge=r(3,23),Ye=r(4,24),Ue=r(53,55),Ve=r(7,27),Ae=r(8,28),Je=r(9,29),_e=r(30,39),U=r(31,39),oe=r(32,39),re=r(33,39),ne=r(34,39),Ke=r(35,39),Xe=r(36,39),se=r(37,39),ae=r(90,39),ie=r(40,49),Ze=r(41,49),Oe=r(42,49),et=r(43,49),tt=r(44,49),ot=r(45,49),rt=r(46,49),le=r(47,49),nt=r(100,49),st=r(91,39),ce=r(92,39),at=r(93,39),it=r(94,39),lt=r(95,39),ct=r(96,39),gt=r(97,39),dt=r(101,49),ut=r(102,49),ft=r(103,49),mt=r(104,49),bt=r(105,49),wt=r(106,49),ht=r(107,49);import{createReadStream as Qe,createWriteStream as ge}from"node:fs";import{stat as We,unlink as de,rename as ue,writeFile as V,mkdir as ze}from"node:fs/promises";import{parse as Te,join as Q,resolve as ke,dirname as Pe}from"node:path";import{createGzip as Re}from"node:zlib";import{pipeline as Ce}from"node:stream/promises";var fe=["deleteOld","archiveOld"],q=["trace","debug","info","success","warn","error","fatal"],me={trace:s=>ce(ie(s)),debug:ae,info:ne,success:oe,warn:re,error:U,fatal:s=>le(U(s)),default:se},W=(s,t=2)=>String(s).padStart(t,"0"),je=()=>{let s=new Date;return`${s.getFullYear()}-${W(s.getMonth()+1)}-${W(s.getDate())} ${W(s.getHours())}:${W(s.getMinutes())}:${W(s.getSeconds())}.${W(s.getMilliseconds(),3)}`},be=(s,t)=>`[${s}] [${je()}] - ${String(t)}`,l=(s,t)=>{if(!s)throw new Error(t)},A=s=>{if((s==null?void 0:s.code)!=="ENOENT")throw s},qe=({consoleOutput:s={enabled:!0,coloredCoding:!0},fileOutput:t={enabled:!1},formatter:L=be,minLevel:f="info",filters:m={}}={})=>{if(l(!s||typeof s=="object","consoleOutput must be an object."),l(typeof s.enabled=="boolean","consoleOutput.enabled must be a boolean."),s.enabled&&typeof s.coloredCoding<"u"&&l(typeof s.coloredCoding=="boolean","consoleOutput.coloredCoding must be a boolean."),l(!t||typeof t=="object","fileOutput must be an object."),l(typeof t.enabled=="boolean","fileOutput.enabled must be a boolean."),t.enabled){l(t.logFilePath,"File logging is enabled but no log file path provided."),l(typeof t.logFilePath=="string","fileOutput.logFilePath must be a string.");let e=t.rotate??!1,o=t.onQueueFull??"drop",n=t.queueFullTimeoutMs??5e3;l(typeof e=="boolean","fileOutput.rotate must be a boolean."),l(o==="drop"||o==="block",'fileOutput.onQueueFull must be "drop" or "block".'),l(typeof n=="number"&&n>=0,"fileOutput.queueFullTimeoutMs must be a non-negative number."),typeof t.maxQueueSize<"u"&&l(typeof t.maxQueueSize=="number"&&Number.isFinite(t.maxQueueSize)&&t.maxQueueSize>=1,"fileOutput.maxQueueSize must be a positive finite number."),e&&(l(typeof t.maxLogSize=="number"&&t.maxLogSize>=1,"Invalid maxLogSize value for file rotation."),l(typeof t.maxLogFiles=="number"&&t.maxLogFiles>=1,"Invalid maxLogFiles value for file rotation."),l(typeof t.onMaxLogFilesReached=="string","Invalid onMaxFilesReached for file rotation."),l(fe.includes(t.onMaxLogFilesReached),`Invalid value for onMaxLogFilesReached: "${t.onMaxLogFilesReached}". Valid values are: ${fe.join(", ")}.`))}typeof L!="function"&&(console.warn("Formatter passed is not a function. Using default formatter"),L=be),l(typeof f=="string","minLevel must be a string."),l(q.includes(f),`Invalid value for minLevel: ${f}. Valid levels are: ${q.join(", ")}.`);let h=null,b=null;if(typeof m.include<"u"){l(typeof m.include=="string","filters.include must be a string.");try{h=new RegExp(m.include)}catch(e){throw new Error(`filters.include is not a valid RegExp: ${e.message}`)}}if(typeof m.exclude<"u"){l(typeof m.exclude=="string","filters.exclude must be a string.");try{b=new RegExp(m.exclude)}catch(e){throw new Error(`filters.exclude is not a valid RegExp: ${e.message}`)}}let z=q.indexOf(f.toLowerCase()),p=t.enabled?t.rotate??!1:!1,w=t.enabled?ke(t.logFilePath):null,J=p?t.maxLogSize:null,we=t.enabled?t.onQueueFull??"drop":"drop",_=t.enabled?t.queueFullTimeoutMs??5e3:5e3,N=t.enabled?t.maxQueueSize??1e5:1e5,u=null,E=null,S=[],F=0,T=!1,I=0,k=!1,y=0,c=null,K=0,R=[],P=0,he=5,D=!1,g={messagesLogged:0,bytesWritten:0,queueHighWaterMark:0,writeFailures:0,averageWriteTime:0,rotations:0,lastWriteTime:0,writeLatencies:[],e:0},ye=e=>e.map(o=>{if(o instanceof Error)return o.stack||o.toString();if(o===null)return"null";if(o===void 0)return"undefined";if(typeof o=="bigint")return`${o}n`;if(typeof o=="string")return o;if(typeof o!="object")return String(o);let n=new WeakSet;try{return JSON.stringify(o,(i,a)=>{if(typeof a=="bigint")return`${a}n`;if(typeof a=="object"&&a){if(n.has(a))return"[Circular Reference]";n.add(a)}return a})}catch{return"[Non-serializable]"}}).join(" "),$=()=>S.length-F,H=()=>{if(R.length!==0&&$()<N){let e=R;R=[];for(let o of e)o.resolve()}},xe=async()=>{if(!t.enabled||u&&!u.writableEnded)return;let e=Pe(w);await ze(e,{recursive:!0});try{y=(await We(w)).size}catch(n){if((n==null?void 0:n.code)==="ENOENT")await V(w,"","utf8"),y=0;else throw n}let o=ge(w,{flags:"a"});o.on("error",n=>{c=n,P++,console.error("[deadslog/system] Logging stream error")}),u=o},C=async()=>{if(t.enabled&&!(u&&!u.writableEnded))return E||(E=(async()=>{try{await xe()}catch(e){throw c=e,e}finally{E=null}})()),E},G=async()=>{if(E&&await E,!u)return;let e=u;u=null,await new Promise((o,n)=>{e.end(i=>i?n(i):o())})},X=async({dir:e,name:o,ext:n,max:i,suffix:a})=>{for(let d=i-1;d>=1;d--){let B=Q(e,`${o}.${d}${n}${a}`),x=Q(e,`${o}.${d+1}${n}${a}`);try{await ue(B,x)}catch(Y){A(Y)}}},pe=async(e=!1)=>{if(!(!p||k||!w)){k=!0;try{if(!e&&y<J)return;g.rotations++,await G();let{dir:o,name:n,ext:i}=Te(w);if(t.onMaxLogFilesReached==="deleteOld"){let a=Q(o,`${n}.${t.maxLogFiles}${i}`);try{await de(a)}catch(d){A(d)}await X({dir:o,name:n,ext:i,max:t.maxLogFiles,suffix:""}),await ue(w,Q(o,`${n}.1${i}`)),await V(w,"","utf8"),y=0}else{let a=Q(o,`${n}.${t.maxLogFiles}${i}.gz`);try{await de(a)}catch(B){A(B)}await X({dir:o,name:n,ext:i,max:t.maxLogFiles,suffix:".gz"});let d=Q(o,`${n}.1${i}.gz`);await Ce(Qe(w),Re(),ge(d)),await V(w,"","utf8"),y=0}await C()}catch(o){c=o,console.error("[deadslog/system] Error during log rotation");try{t.enabled&&!u&&await C()}catch{console.error("[deadslog/system] Failed to reopen stream after rotation")}}finally{k=!1}}},Fe=e=>{g.messagesLogged++,g.bytesWritten+=e.length+1,g.queueHighWaterMark=Math.max(g.queueHighWaterMark,$())},$e=e=>{let o=Date.now()-e;if(g.writeLatencies.push(o),g.e+=o,g.writeLatencies.length>100){let n=g.writeLatencies.shift();g.e-=n}g.averageWriteTime=g.writeLatencies.length===0?0:g.e/g.writeLatencies.length,g.lastWriteTime=Date.now()},Le=async()=>{if(!T){T=!0;try{for(;$()>0;){if(P>=he){for(;$()>0;)S[F++].reject(new Error("Too many file system failures; logging disabled until stream recovers."));S.length=0,F=0,H();break}let{message:e,startTime:o,resolve:n,reject:i}=S[F++];F>1024&&F*2>S.length&&(S.splice(0,F),F=0);try{let a=Buffer.byteLength(e,"utf8")+1;if(p&&y>0&&y+a>=J&&await pe(!0),await C(),!u||u.writableEnded){P++,c=c??new Error("File stream is closed."),i(c);continue}await new Promise((d,B)=>{u.write(`${e} `,x=>x?B(x):d())}),P=0,y+=a,Fe(e),$e(o),n()}catch(a){c=a,P++,g.writeFailures++,console.error("[deadslog/system] Error writing to log file");try{await G()}catch{}i(a),H()}}}finally{T=!1,H()}}},Se=async()=>{$()<N||await new Promise((e,o)=>{let n=null;_>0&&(n=setTimeout(()=>{n=null,o(new Error("Timed out waiting for log queue space."))},_)),R.push({resolve:()=>{n&&clearTimeout(n),e()}})})},ve=async(e,o)=>{if($()>=N)if(we==="block")await Se();else return K++,g.writeFailures++,c=c??new Error("Write queue is full. Log message dropped."),Promise.reject(c);return new Promise((n,i)=>{S.push({message:e,startTime:o,resolve:n,reject:i}),Le().catch(a=>{c=a,console.error("[deadslog/system] Queue processor failed")})})},v=async(e,...o)=>{if(!D){I++;try{if(q.indexOf(e)<z)return;let i=e.toUpperCase(),a=ye(o),d=L(i,a);if(b!=null&&b.test(d)||h&&!h.test(d))return;if(s.enabled)if(s.coloredCoding){let x=me[e]||me.default,Y=`[${i}]`,Me=`[${x(i)}]`;console.log(d.replace(Y,Me))}else console.log(d);if(!t.enabled)return;try{await C()}catch(x){throw c=x,x}let B=d.replace(/\r?\n/g,"\\n");return ve(B,Date.now())}finally{I--}}},M=e=>Promise.resolve(e).catch(o=>{c=o,console.error("[deadslog/system] Log write failed")}),Z={trace:(...e)=>M(v("trace",...e)),debug:(...e)=>M(v("debug",...e)),info:(...e)=>M(v("info",...e)),success:(...e)=>M(v("success",...e)),warn:(...e)=>M(v("warn",...e)),error:(...e)=>M(v("error",...e)),fatal:(...e)=>M(v("fatal",...e)),flush:async()=>{for(;I>0||$()>0||T||k;)await new Promise(e=>setTimeout(e,25))},destroy:async()=>{D||(D=!0,await Z.flush(),await G())},getMetrics:()=>t.enabled?{...g,currentQueueSize:$(),isProcessingQueue:T,isRotating:k,droppedMessages:K,lastFileError:c?String((c==null?void 0:c.message)??c):null,currentFileBytes:y}:"fileOutput is disabled. No metrics available"};return Z},vt=qe;export{vt as default};