rotating-log (empty) → 0.1
raw patch · 5 files changed
+210/−0 lines, 5 filesdep +basedep +bytestringdep +directorysetup-changed
Dependencies added: base, bytestring, directory, filepath, old-locale, time
Files
- LICENSE +3/−0
- Setup.hs +2/−0
- rotating-log.cabal +40/−0
- src/System/RotatingLog.hs +139/−0
- test/TestRotate.hs +26/−0
+ LICENSE view
@@ -0,0 +1,3 @@+Copyright (c)2013, Soostone Inc++All rights reserved.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ rotating-log.cabal view
@@ -0,0 +1,40 @@+name: rotating-log+version: 0.1+description: Size-limited, concurrent, automatically-rotating log writer for production applications.+license: BSD3+license-file: LICENSE+author: Ozgun Ataman, Doug Beardsley+maintainer: doug.beardsley@soostone.com+copyright: Soostone Inc+category: Logging+build-type: Simple+cabal-version: >= 1.8++library+ hs-source-dirs: src++ exposed-modules: + System.RotatingLog++ build-depends: + base >= 4 && < 5,+ bytestring >= 0.8,+ old-locale >= 1.0,+ time >= 1.1,+ filepath >= 1.0,+ directory >= 1.0+ + ghc-options: -Wall -fwarn-tabs++executable test-rotate+ main-is: TestRotate.hs+ hs-source-dirs: test, src+ build-depends:+ base >= 4,+ bytestring >= 0.8,+ old-locale >= 1.0,+ time >= 1.1,+ filepath >= 1.0,+ directory >= 1.0++ ghc-options: -Wall -fwarn-tabs -threaded
+ src/System/RotatingLog.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module : System.RotatingLog+-- Copyright : Soostone Inc+-- License : BSD3+--+-- Maintainer : admin@soostone.com+-- Stability : experimental+--+-- Convenient logging to a disk-based log file with automatic file+-- rotation based on size.+----------------------------------------------------------------------------++module System.RotatingLog+ (++ -- * Core API+ RotatingLog+ , mkRotatingLog+ , rotatedWrite+ , rotatedWrite'++ -- * Built-In Post-Rotate Actions+ , archiveFile++ ) where++-------------------------------------------------------------------------------+import Control.Concurrent.MVar+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as B+import Data.Time+import Data.Word+import System.Directory+import System.FilePath.Posix+import System.IO+import System.Locale+-------------------------------------------------------------------------------+++------------------------------------------------------------------------------+-- | A size-limited rotating log. Log filenames are of the format+-- prefix_timestamp.log.+data RotatingLog = RotatingLog+ { logInfo :: MVar LogInfo+ , namePrefix :: String+ , sizeLimit :: Word64+ , postAction :: FilePath -> IO ()+ }+++data LogInfo = LogInfo+ { curHandle :: Handle+ , bytesWritten :: !Word64+ }+++curLogFileName :: String -> FilePath+curLogFileName = (++".log")+++logFileName :: String -> UTCTime -> FilePath+logFileName pre t = concat+ [pre, "_", formatTime defaultTimeLocale "%Y_%m_%d_%H_%M_%S%Q" t, ".log"]+++------------------------------------------------------------------------------+-- | Creates a rotating log given a prefix and size limit in bytes.+mkRotatingLog+ :: String+ -- ^ A prefix for the written log files.+ -> Word64+ -- ^ A size limit in bytes.+ -> (FilePath -> IO ())+ -- ^ An action to be performed on the finished file following+ -- rotation. For example, you could give a callback that moves or+ -- ships the files somewhere else.+ -> IO RotatingLog+mkRotatingLog pre limit pa = do+ let fp = curLogFileName pre+ h <- openFile fp AppendMode+ len <- hFileSize h+ mvar <- newMVar $ LogInfo h (fromIntegral len)+ return $ RotatingLog mvar pre limit pa+++------------------------------------------------------------------------------+-- | Like "rotatedWrite'", but doesn't need a UTCTime and obtains it+-- with a syscall.+rotatedWrite :: RotatingLog -> ByteString -> IO ()+rotatedWrite rlog bs = do+ t <- getCurrentTime+ rotatedWrite' rlog t bs+++------------------------------------------------------------------------------+-- | Writes ByteString to a rotating log file. If this write would exceed the+-- size limit, then the file is closed and a new file opened. This function+-- takes a UTCTime to allow a cached time to be used to avoid a system call.+--+-- Please note this function does NOT implicitly insert a newline at+-- the end of the string you provide. This is so that it can be used+-- to log non-textual streams such as binary serialized or compressed+-- content.+rotatedWrite' :: RotatingLog -> UTCTime -> ByteString -> IO ()+rotatedWrite' RotatingLog{..} t bs = do+ modifyMVar_ logInfo $ \LogInfo{..} -> do+ (h,b) <- if bytesWritten + len > sizeLimit+ then do hClose curHandle+ let newFile = logFileName namePrefix t+ renameFile curFile newFile+ postAction newFile+ h <- openFile curFile AppendMode+ return (h, 0)+ else return (curHandle, bytesWritten)+ B.hPutStr h bs+ return $! LogInfo h (len + b)+ where+ len = fromIntegral $ B.length bs+ curFile = curLogFileName namePrefix+++-------------------------------------------------------------------------------+-- | A built-in post-rotate action that moves the finished file to a+-- given archive location.+archiveFile+ :: FilePath+ -- ^ A target archive directory+ -> (FilePath -> IO ())+archiveFile archive fp =+ let (_, fn) = splitFileName fp+ target = archive </> fn+ in do+ createDirectoryIfMissing True archive+ renameFile fp target+++
+ test/TestRotate.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Concurrent+import qualified Data.ByteString.Char8 as B+import System.RotatingLog++thread :: RotatingLog -> B.ByteString -> Int -> IO ()+thread _log pre n = do+ rotatedWrite _log (B.concat [pre, ": ", B.pack $ show n, "\n"])+ thread _log pre (n+1)++main :: IO ()+main = do+ _log <- mkRotatingLog "foo" 1000000 (archiveFile "logs")+ a <- forkIO $ thread _log "a" 0+ b <- forkIO $ thread _log "b" 0+ c <- forkIO $ thread _log "c" 0+ d <- forkIO $ thread _log "d" 0+ threadDelay 5000000+ killThread a+ killThread b+ killThread c+ killThread d+ putStrLn "done"