8 | module Log4Types.File.Rotation
12 | import System.File.Meta
13 | import Log4Types.Core
14 | import Log4Types.File
22 | %foreign "C:rename,libc 6"
23 | "node:lambda:(from, to) => { try { require('fs').renameSync(from, to); return 0; } catch (e) { return -1; } }"
24 | prim__rename : (from : String) -> (to : String) -> PrimIO Int
31 | renameFile : HasIO io => (from : String) -> (to : String) -> io (Either FileError ())
32 | renameFile from to = do
33 | res <- primIO (prim__rename from to)
34 | if res == 0 then pure (Right ()) else pure (Left (GenericFileError res))
42 | record RotationConfig where
43 | constructor MkRotationConfig
52 | defaultRotation : RotationConfig
53 | defaultRotation = MkRotationConfig 10_000_000 5
59 | record RotatingState where
60 | constructor MkRotatingState
62 | config : RotationConfig
63 | handleRef : IORef File
64 | bytesRef : IORef Integer
66 | rotatedPath : String -> Nat -> String
67 | rotatedPath base i = base ++ "." ++ show i
71 | shiftRotated : HasIO io => (basePath : String) -> (start : Nat) -> io ()
72 | shiftRotated _ Z = pure ()
73 | shiftRotated base (S k) = do
74 | let source = rotatedPath base (S k)
75 | let target = rotatedPath base (S (S k))
76 | ignore $
removeFile target
77 | ignore $
renameFile source target
80 | rotate : HasIO io => RotatingState -> io ()
82 | current <- readIORef st.handleRef
83 | ignore $
closeFile current
84 | case st.config.maxFiles of
86 | S k => shiftRotated st.basePath k
87 | ignore $
renameFile st.basePath (rotatedPath st.basePath 1)
88 | Right fresh <- openFile st.basePath WriteTruncate
90 | writeIORef st.handleRef fresh
91 | writeIORef st.bytesRef 0
93 | rotateIfNeeded : HasIO io => RotatingState -> io ()
94 | rotateIfNeeded st = do
95 | bytes <- readIORef st.bytesRef
96 | when (bytes >= st.config.maxBytes && st.config.maxFiles > 0) (rotate st)
98 | writeAndCount : HasIO io => RotatingState -> String -> io ()
99 | writeAndCount st msg = do
100 | current <- readIORef st.handleRef
101 | ignore $
fPutStrLn current msg
102 | ignore $
fflush current
103 | modifyIORef st.bytesRef (+ cast (length msg + 1))
120 | withRotatingLogFile
122 | => (basePath : String)
123 | -> (config : RotationConfig)
124 | -> (LogAction io String -> io a)
125 | -> io (Either FileError a)
126 | withRotatingLogFile basePath config k = do
127 | Right h <- openFile basePath Append
128 | | Left err => pure (Left err)
129 | initialBytes <- case !(fileSize h) of
130 | Right n => pure (cast {to = Integer} n)
133 | bRef <- newIORef initialBytes
134 | let st = MkRotatingState basePath config hRef bRef
135 | result <- k (MkLogAction (writeAndCount st))
136 | finalH <- readIORef hRef
137 | ignore $
closeFile finalH
138 | pure (Right result)