File: log.ml

package info (click to toggle)
xen-api-libs 0.5.2-3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 1,940 kB
  • sloc: ml: 13,925; sh: 2,930; ansic: 1,699; makefile: 1,240; python: 83
file content (294 lines) | stat: -rw-r--r-- 8,556 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
(*
 * Copyright (C) 2006-2009 Citrix Systems Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 only. with the special
 * exception on linking described in file LICENSE.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *)

open Printf

module Mutex = struct
    include Mutex
    let execute lock f =
    	Mutex.lock lock;
    	let r = begin try f () with exn -> Mutex.unlock lock; raise exn end; in
    	Mutex.unlock lock;
    	r
end

exception Unknown_level of string

type stream_type = Stderr | Stdout | File of string

type stream_log = {
  ty : stream_type;
  channel : out_channel option ref;
  mutex : Mutex.t;
}

type level = Debug | Info | Warn | Error

type output =
	| Stream of stream_log
	| String of string list ref
	| Syslog of string
	| Nil

let int_of_level l =
	match l with Debug -> 0 | Info -> 1 | Warn -> 2 | Error -> 3

let string_of_level l =
	match l with Debug -> "debug" | Info -> "info"
	           | Warn -> "warn" | Error -> "error"

let level_of_string s =
	match s with
	| "debug" -> Debug
	| "info"  -> Info
	| "warn"  -> Warn
	| "error" -> Error
	| _       -> raise (Unknown_level s)

let mkdir_safe dir perm =
        try Unix.mkdir dir perm with _ -> ()

let mkdir_rec dir perm =
	let rec p_mkdir dir =
		let p_name = Filename.dirname dir in
		if p_name = "/" || p_name = "." then
			()
		else (
			p_mkdir p_name;
			mkdir_safe dir perm
		) in
	p_mkdir dir

type t = { output: output; mutable level: level; }

let get_strings t = match t.output with
	| String s -> !s
	| _ -> []

let get_level t = t.level

let make output level = { output = output; level = level; }

let make_stream ty channel = 
        Stream {ty=ty; channel=ref channel; mutex=Mutex.create ()}

(** open a syslog logger *)
let opensyslog k level =
	make (Syslog k) level

(** open a stderr logger *)
let openerr level =
	if (Unix.stat "/dev/stderr").Unix.st_kind <> Unix.S_CHR then
		failwith "/dev/stderr is not a valid character device";
	make (make_stream Stderr (Some (open_out "/dev/stderr"))) level
	
let openout level =
	if (Unix.stat "/dev/stdout").Unix.st_kind <> Unix.S_CHR then
		failwith "/dev/stdout is not a valid character device";
        make (make_stream Stdout (Some (open_out "/dev/stdout"))) level


(** open a stream logger - returning the channel. *)
(* This needs to be separated from 'openfile' so we can reopen later *)
let doopenfile filename =
        if Filename.is_relative filename then
	        None
	else (
                try
		  mkdir_rec (Filename.dirname filename) 0o700;
	          Some (open_out_gen [ Open_append; Open_creat ] 0o600 filename)
                with _ -> None
	)

(** open a stream logger - returning the output type *)
let openfile filename level =
        make (make_stream (File filename) (doopenfile filename)) level

(** open a nil logger *)
let opennil () =
	make Nil Error

(** open a string logger *)
let openstring level =
        make (String (ref [""])) level

(** try to reopen a logger *)
let reopen t =
	match t.output with
	| Nil              -> t
	| Syslog k         -> Syslog.close (); opensyslog k t.level
	| Stream s         -> Mutex.execute s.mutex 
	    (fun () -> 
	      match (s.ty,!(s.channel)) with 
		| (File filename, Some c) -> close_out c; s.channel := (try doopenfile filename with _ -> None); t 
		| _ -> t)
	| String _         -> t

(** close a logger *)
let close t =
	match t.output with
	| Nil           -> ()
	| Syslog k      -> Syslog.close ();
	| Stream s      -> 
	    Mutex.execute s.mutex (fun () -> 
	      match !(s.channel) with 
		| Some c -> close_out c; s.channel := None
		| None -> ())
	| String _      -> ()

(** create a string representating the parameters of the logger *)
let to_string t =
	match t.output with
	| Nil           -> "nil"
	| Syslog k      -> sprintf "syslog:%s" k
	| String _      -> "string"
	| Stream s      -> 
	    begin
	      match s.ty with 
		| File f -> sprintf "file:%s" f
		| Stderr -> "stderr"
		| Stdout -> "stdout"
	    end

(** parse a string to a logger *)
let of_string s : t =
	match s with
	| "nil"    -> opennil ()
	| "stderr" -> openerr Debug
	| "stdout" -> openout Debug
	| "string" -> openstring Debug
	| _        ->
		let split_in_2 s =
			try
				let i = String.index s ':' in
				String.sub s 0 (i),
				String.sub s (i + 1) (String.length s - i - 1)
			with _ ->
				failwith "logger format error: expecting string:string"
			in
		let k, s = split_in_2 s in
		match k with
		| "syslog" -> opensyslog s Debug
		| "file"   -> openfile s Debug
		| _        -> failwith "unknown logger type"

let validate s =
	match s with
	| "nil"    -> ()
	| "stderr" -> ()
	| "stdout" -> ()
	| "string" -> ()
	| _        ->
		let split_in_2 s =
			try
				let i = String.index s ':' in
				String.sub s 0 (i),
				String.sub s (i + 1) (String.length s - i - 1)
			with _ ->
				failwith "logger format error: expecting string:string"
			in
		let k, s = split_in_2 s in
		match k with
		| "syslog" -> ()
		| "file"   -> (
			try
				let st = Unix.stat s in
				if st.Unix.st_kind <> Unix.S_REG then
					failwith "logger file is a directory";
				()
			with Unix.Unix_error (Unix.ENOENT, _, _) -> ()
			)
		| _        -> failwith "unknown logger"

(** change a logger level to level *)
let set t level = t.level <- level

let gettimestring () =
	let time = Unix.gettimeofday () in
	let tm = Unix.gmtime time in
        let msec = time -. (floor time) in
	sprintf "%d%.2d%.2dT%.2d:%.2d:%.2d.%.3dZ|" (1900 + tm.Unix.tm_year)
	        (tm.Unix.tm_mon + 1) tm.Unix.tm_mday
	        tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec
	        (int_of_float (1000.0 *. msec))

(*let extra_hook = ref (fun x -> x)*)

let filesize = ref 0 
let mutex = Mutex.create ()

let output_common t ?(raw=false) ?(syslog_time=false) ?(key="") ?(extra="") priority (message: string) =
  let result_string = ref "" in
  let construct_string withtime =
		(*let key = if key = "" then [] else [ key ] in
		let extra = if extra = "" then [] else [ extra ] in
		let items = 
      (if withtime then [ gettimestring () ] else [])
		  @ [ sprintf "%5s" (string_of_level priority) ] @ extra @ key @ [ message ] in
(*		let items = !extra_hook items in*)
		String.concat " " items*)
		result_string := (
			if raw
			then Printf.sprintf "%s" message
			else
    Printf.sprintf "[%s%.5s|%s] %s" 
      (if withtime then gettimestring () else "") (string_of_level priority) extra message
    );
    !result_string
	in
	(* Keep track of how much we write out to streams, so that we can *)
	(* log-rotate at appropriate times *)
	let write_to_stream stream =
	  let string = (construct_string true) in
	  Mutex.execute mutex 
	    (fun () -> filesize := !filesize + (String.length string));
	  fprintf stream "%s\n%!" string;
        in

	if String.length message > 0 then
	(match t.output with
	| Syslog k      ->
		let sys_prio = match priority with
		| Debug -> Syslog.Debug
		| Info  -> Syslog.Info
		| Warn  -> Syslog.Warning
		| Error -> Syslog.Err in
		let facility = try Syslog.facility_of_string k with _->Syslog.Daemon in
		Syslog.log facility sys_prio ((construct_string syslog_time) ^ "\n")
	| Stream s -> Mutex.execute s.mutex 
	    (fun () -> 
	      match !(s.channel) with
		| Some c -> write_to_stream c
		| None -> ())
	| Nil           -> ()
	| String s      -> (s := (construct_string true)::!s)
	);
	!result_string

let output t ?(key="") ?(extra="") priority (message: string) =
	ignore(output_common t ~key ~extra priority message)

let output_and_return t ?(raw=false) ~syslog_time ?(key="") ?(extra="") priority (message: string) =
	output_common t ~raw ~syslog_time ~key ~extra priority message

let log t level (fmt: ('a, unit, string, unit) format4): 'a =
	let b = (int_of_level t.level) <= (int_of_level level) in
	(* ksprintf is the preferred name for kprintf, but the former
	 * is not available in OCaml 3.08.3 *)
	Printf.kprintf (if b then output t level else (fun _ -> ())) fmt

let debug t (fmt: ('a , unit, string, unit) format4) = log t Debug fmt
let info t (fmt: ('a , unit, string, unit) format4) = log t Info fmt
let warn t (fmt: ('a , unit, string, unit) format4) = log t Warn fmt
let error t (fmt: ('a , unit, string, unit) format4) = log t Error fmt