| 1 |
/*************************************************************** |
|---|
| 2 |
Copyright (c) Steve Teale 2007 |
|---|
| 3 |
This program is free software; you can use it for any purpose |
|---|
| 4 |
subject to the following conditions. |
|---|
| 5 |
|
|---|
| 6 |
This program is distributed in the hope that it will be useful, |
|---|
| 7 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 8 |
MERCHANTABILITY or FITNESS FOR ANY PARTICULAR PURPOSE. |
|---|
| 9 |
****************************************************************/ |
|---|
| 10 |
module bevutils.log4d; |
|---|
| 11 |
|
|---|
| 12 |
import std.stdio; |
|---|
| 13 |
import std.file; |
|---|
| 14 |
import std.date; |
|---|
| 15 |
import std.stream; |
|---|
| 16 |
import std.regexp; |
|---|
| 17 |
import std.string; |
|---|
| 18 |
|
|---|
| 19 |
/** |
|---|
| 20 |
* General purpose logging class with file count and size limits and rollover. |
|---|
| 21 |
* |
|---|
| 22 |
* The name implies no similarity to the workings of log4j |
|---|
| 23 |
*/ |
|---|
| 24 |
class Log4D |
|---|
| 25 |
{ |
|---|
| 26 |
private: |
|---|
| 27 |
const char[] _rex = r"2[0-9]{3}[0-1][0-9][0-3][0-9][0-2][0-9][0-5][0-9][0-5][0-9][0-9]{3}\.log"; |
|---|
| 28 |
uint _maxsize; |
|---|
| 29 |
uint _maxfiles; |
|---|
| 30 |
char[] _path; |
|---|
| 31 |
char[] _prefix; |
|---|
| 32 |
char[] _current; |
|---|
| 33 |
int _numlogs; |
|---|
| 34 |
bool _ok, _isopen; |
|---|
| 35 |
int _cycle; |
|---|
| 36 |
File _log; |
|---|
| 37 |
|
|---|
| 38 |
private char[] makeLogName() |
|---|
| 39 |
{ |
|---|
| 40 |
d_time t = UTCtoLocalTime(getUTCtime()); |
|---|
| 41 |
Date d; |
|---|
| 42 |
d.fromTicks(t); |
|---|
| 43 |
char[] ds = std.string.format("%d%02d%02d%02d%02d%02d%03d", d.year, d.month, d.day, d.hour, d.minute, d.second, d.ms); |
|---|
| 44 |
return std.string.format("%s\\%s%s.log", _path, _prefix, ds); |
|---|
| 45 |
} |
|---|
| 46 |
|
|---|
| 47 |
private void findCurrent() |
|---|
| 48 |
{ |
|---|
| 49 |
char[][] list = listthisdir(_path, RegExp(_prefix ~ _rex)); |
|---|
| 50 |
_numlogs = list.length; |
|---|
| 51 |
if (_numlogs) |
|---|
| 52 |
{ |
|---|
| 53 |
list = list.sort; |
|---|
| 54 |
_current = list[_numlogs-1]; |
|---|
| 55 |
} |
|---|
| 56 |
else |
|---|
| 57 |
{ |
|---|
| 58 |
_current = makeLogName(); |
|---|
| 59 |
_numlogs = 1; |
|---|
| 60 |
} |
|---|
| 61 |
} |
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 |
private bool rollOver() |
|---|
| 65 |
{ |
|---|
| 66 |
if (_isopen) |
|---|
| 67 |
{ |
|---|
| 68 |
_log.close(); |
|---|
| 69 |
_isopen = false; |
|---|
| 70 |
} |
|---|
| 71 |
ulong sz = getSize(_current); |
|---|
| 72 |
if (sz < _maxsize) |
|---|
| 73 |
{ |
|---|
| 74 |
_log = new File(_current, std.stream.FileMode.Append); |
|---|
| 75 |
_isopen = true; |
|---|
| 76 |
return false; |
|---|
| 77 |
} |
|---|
| 78 |
char[] newname = makeLogName(); |
|---|
| 79 |
if (newname == _current) |
|---|
| 80 |
{ |
|---|
| 81 |
// We've filled a log file in less than a millisecond - increase _maxsize |
|---|
| 82 |
// but in this case, just lie and continue to grow the existing file |
|---|
| 83 |
return true; |
|---|
| 84 |
} |
|---|
| 85 |
_current = newname; |
|---|
| 86 |
// Create empty file |
|---|
| 87 |
std.file.write(_current, cast(void[])""); |
|---|
| 88 |
_numlogs++; |
|---|
| 89 |
|
|---|
| 90 |
if (_numlogs > _maxfiles) |
|---|
| 91 |
{ |
|---|
| 92 |
int n = _numlogs - _maxfiles; |
|---|
| 93 |
char[][] list = listthisdir(_path, RegExp(_prefix ~ _rex)); |
|---|
| 94 |
if (list.length) |
|---|
| 95 |
{ |
|---|
| 96 |
list = list.sort; |
|---|
| 97 |
for (int i = 0; i < n; i++) |
|---|
| 98 |
{ |
|---|
| 99 |
std.file.remove(list[i]); |
|---|
| 100 |
} |
|---|
| 101 |
_numlogs = _maxfiles; |
|---|
| 102 |
} |
|---|
| 103 |
else |
|---|
| 104 |
_numlogs = 0; |
|---|
| 105 |
} |
|---|
| 106 |
|
|---|
| 107 |
return true; |
|---|
| 108 |
} |
|---|
| 109 |
|
|---|
| 110 |
public: |
|---|
| 111 |
/** |
|---|
| 112 |
* At the moment there is only one logging style, but please feel free to roll your own. |
|---|
| 113 |
*/ |
|---|
| 114 |
enum Log4DStyle |
|---|
| 115 |
{ |
|---|
| 116 |
STD |
|---|
| 117 |
} |
|---|
| 118 |
|
|---|
| 119 |
/** |
|---|
| 120 |
* Single constructor |
|---|
| 121 |
* |
|---|
| 122 |
* Log files are named with a prefix to indicate their host application, and a timestamp to |
|---|
| 123 |
* the millisecond. |
|---|
| 124 |
* |
|---|
| 125 |
* Params: |
|---|
| 126 |
* path = Path to the directory where log files are to be kept |
|---|
| 127 |
* prefix = Distinguishing prefix for the log files - usually an abbreviation of the host application name. |
|---|
| 128 |
* style = One of possibly several styles mentioned in enum Log4DStyle - only one now. |
|---|
| 129 |
* maxfiles = Maximum number of log files to retain at any time. |
|---|
| 130 |
* maxsize = Size at which to rollover and create a new log file. |
|---|
| 131 |
*/ |
|---|
| 132 |
this(char[] path, char[] prefix, Log4DStyle style = Log4DStyle.STD, int maxfiles = 0, int maxsize = 0) |
|---|
| 133 |
{ |
|---|
| 134 |
_ok = false; |
|---|
| 135 |
_isopen = false; |
|---|
| 136 |
_cycle = 0; |
|---|
| 137 |
_log = null; |
|---|
| 138 |
_maxsize = (maxsize == 0)? 0x400000: maxsize; |
|---|
| 139 |
if (_maxsize < 0x100000) |
|---|
| 140 |
_maxsize = 0x100000; // Get a bigger hard drive ;=) |
|---|
| 141 |
_maxfiles = (maxfiles == 0)? 5: maxfiles; |
|---|
| 142 |
int l = path.length; |
|---|
| 143 |
if (path[l-1] == '\\') |
|---|
| 144 |
path.length = l-1; |
|---|
| 145 |
_path = path; |
|---|
| 146 |
_prefix = prefix; |
|---|
| 147 |
_current = ""; |
|---|
| 148 |
_numlogs = 0; |
|---|
| 149 |
try |
|---|
| 150 |
{ |
|---|
| 151 |
findCurrent(); |
|---|
| 152 |
_log = new File(_current, std.stream.FileMode.Append); |
|---|
| 153 |
_isopen = true; |
|---|
| 154 |
_ok = true; |
|---|
| 155 |
} |
|---|
| 156 |
catch (Exception) {} |
|---|
| 157 |
} |
|---|
| 158 |
|
|---|
| 159 |
~this() { _log.close(); } |
|---|
| 160 |
|
|---|
| 161 |
/** |
|---|
| 162 |
* Sentinel to check that the logger was created OK. |
|---|
| 163 |
*/ |
|---|
| 164 |
public bool OK() { return _ok; } |
|---|
| 165 |
|
|---|
| 166 |
|
|---|
| 167 |
/** |
|---|
| 168 |
* The method to log messages. |
|---|
| 169 |
* |
|---|
| 170 |
* Override this to create you own message formats |
|---|
| 171 |
* |
|---|
| 172 |
* This version creates log entries like:<br> |
|---|
| 173 |
* "[YYYY-MM-DD HH:MM:SS:mmm] INFO: The message" |
|---|
| 174 |
* |
|---|
| 175 |
* Params: |
|---|
| 176 |
* msgType = A string to label the message after the timestamp - e.g. INFO |
|---|
| 177 |
* msg = The text to be logged |
|---|
| 178 |
*/ |
|---|
| 179 |
public synchronized void logMessage(char[] msgType, char[] msg) |
|---|
| 180 |
{ |
|---|
| 181 |
// Check occasionally to see if the log file is over its stipulated size, |
|---|
| 182 |
// and start a new one if it is. |
|---|
| 183 |
if (_cycle > 20) |
|---|
| 184 |
{ |
|---|
| 185 |
if (rollOver()) |
|---|
| 186 |
{ |
|---|
| 187 |
_log = new File(_current, std.stream.FileMode.Append); |
|---|
| 188 |
_isopen = true; |
|---|
| 189 |
_cycle = 0; |
|---|
| 190 |
} |
|---|
| 191 |
} |
|---|
| 192 |
else |
|---|
| 193 |
_cycle++; |
|---|
| 194 |
|
|---|
| 195 |
d_time t = UTCtoLocalTime(getUTCtime()); |
|---|
| 196 |
Date d; |
|---|
| 197 |
d.fromTicks(t); |
|---|
| 198 |
char[] ds = std.string.format("[%d-%02d-%02d %02d:%02d:%02d:%03d] %s: %s", |
|---|
| 199 |
d.year, d.month, d.day, d.hour, d.minute, d.second, d.ms, msgType, msg); |
|---|
| 200 |
|
|---|
| 201 |
_log.writeLine(ds); |
|---|
| 202 |
} |
|---|
| 203 |
} |
|---|
| 204 |
|
|---|
| 205 |
/+ |
|---|
| 206 |
void main(char[][] args) |
|---|
| 207 |
{ |
|---|
| 208 |
Log4D log = new Log4D(r"d:\aaa", "XYZ"); |
|---|
| 209 |
for (int i = 0; i < 2000000; i++) |
|---|
| 210 |
log.logMessage("INFO", "A somewhat long and pointless message simply intended to fill up reams of log file and otherwise to achieve nothing."); |
|---|
| 211 |
} |
|---|
| 212 |
+/ |
|---|