Skip to content

Commit e0b576f

Browse files
Merge branch 'master' into lwt6-compat
* master: [Files.save_as]: just write directly to special files (#60) web: expose http_request_k (#58) structured logging (#59) log: add critical due to error level inflation ci: use ocaml 5.4 files: add mkdir_p (#57) [Action.config_lines] document and test behavior of commented lines (#56) update to trace 0.12 and OTEL main web: track content-type fix: avoid curl.ml crash web: track more information about request/response in span
2 parents de70637 + 91ff69d commit e0b576f

18 files changed

Lines changed: 556 additions & 163 deletions

.github/workflows/makefile.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
matrix:
1515
ocaml-version:
1616
- 4.14
17-
- 5.2
17+
- 5.4
1818

1919
runs-on: ubuntu-22.04
2020

@@ -37,7 +37,7 @@ jobs:
3737

3838
- name: Pin libevent
3939
run: opam pin add libevent --dev
40-
40+
4141
- name: Build
4242
run: opam exec -- dune build --profile=release
4343

action.mli

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,16 @@ val io_null : unit IO.output
185185
val file_lines_exn : string -> string list
186186
val file_lines : string -> string list
187187

188-
(** read lines from file skipping empty lines and comments (lines starting with '#') *)
188+
(** Read lines from file skipping empty lines and comments (lines starting with '#').
189+
190+
Moreover, comments are stripped from lines (all characters
191+
including and following a '#') and returned lines are trimmed. *)
189192
val make_config_lines : string list -> string list
190193
val config_lines_exn : string -> string list
194+
195+
(** [config_lines file] read [file] and return the config lines.
196+
197+
See {!make_config_lines} for details on config lines. *)
191198
val config_lines : string -> string list
192199

193200

daemon.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ let unless_exit x = Lwt.pick [wait_exit (); x]
6262
let get_args () =
6363
[
6464
("-loglevel", Arg.String Log.set_loglevels, " ([<facil|prefix*>=]debug|info|warn|error[,])+");
65+
("-logformat",
66+
Arg.Symbol (["plain"; "default"; "logfmt"], (function
67+
| "plain" | "default" -> Log.State.set_plaintext ()
68+
| "logfmt" -> Log.State.set_logfmt ()
69+
| s -> failwith (Printf.sprintf "unknown log format %S" s))),
70+
" Log output format (default: plain)");
6571
ExtArg.may_str "logfile" logfile "<file> Log file";
6672
ExtArg.may_str "pidfile" pidfile "<file> PID file";
6773
"-runas",

devkit.opam

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ depends: [
2222
"pcre2" {>= "8.0.3"}
2323
"trace" {>= "0.12"}
2424
"extunix" {>= "0.1.4"}
25-
"lwt" {>= "6.0.0"}
25+
"lwt" {>= "5.10.0"}
2626
"lwt_ppx"
2727
"base-bytes"
2828
"base-unix"
@@ -38,6 +38,6 @@ depopts: [
3838
]
3939
conflicts: [
4040
"jemalloc" {< "0.2"}
41-
"opentelemetry" {< "0.6"}
41+
"opentelemetry" {< "0.13"}
4242
]
4343
available: arch != "arm32" & arch != "x86_32"

dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868

6969
(executable
7070
(name test)
71-
(libraries lwt lwt.unix devkit extlib extunix libevent ocamlnet_lite ounit2 unix yojson)
71+
(libraries lwt lwt.unix devkit extlib extunix libevent ocamlnet_lite ounit2 threads unix yojson)
7272
(modules test test_httpev))
7373

7474
; uses 8GB+ RAM, so do not run as part of test suite

files.ml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,19 @@ let () =
5555
iter_files "/etc" (fun s _ -> print_endline s)
5656
*)
5757

58-
let save_as name ?(mode=0o644) f =
58+
let mkdir_p ?(perm=0o755) path =
59+
let rec aux path =
60+
if Sys.file_exists path then begin
61+
if not (Sys.is_directory path) then
62+
Exn.fail "mkdir_p: %s exists but is not a directory" path
63+
end else begin
64+
aux (Filename.dirname path);
65+
try Unix.mkdir path perm with Unix.Unix_error (Unix.EEXIST, _, _) -> ()
66+
end
67+
in
68+
aux path
69+
70+
let save_as_regular name ?(mode=0o644) f =
5971
(* not using make_temp_file cause same dir is needed for atomic rename *)
6072
let temp = Printf.sprintf "%s.save.%d.tmp" name (U.gettid ()) in
6173
bracket (Unix.openfile temp [Unix.O_WRONLY;Unix.O_CREAT] mode) Unix.close begin fun fd ->
@@ -69,3 +81,9 @@ let save_as name ?(mode=0o644) f =
6981
with
7082
exn -> Exn.suppress Unix.unlink temp; raise exn
7183
end
84+
85+
let rec save_as name ?mode f =
86+
match (Unix.lstat name).st_kind with
87+
| Unix.S_LNK -> save_as (Unix.realpath name) ?mode f
88+
| Unix.S_REG | (exception Unix.Unix_error (Unix.ENOENT, _, _)) -> save_as_regular name ?mode f
89+
| _ -> Out_channel.with_open_gen [ Open_wronly ] 0 name f

files.mli

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,22 @@ val iter_files : string -> (string -> in_channel -> unit) -> unit
1616
val open_out_append_bin : string -> out_channel
1717
val open_out_append_text : string -> out_channel
1818

19+
(** [mkdir_p ?perm path] creates directory [path] and all missing parent
20+
directories. Similar to [mkdir -p]. Raises [Failure] if [path] exists
21+
but is not a directory. Default [perm] is [0o755]. *)
22+
val mkdir_p : ?perm:Unix.file_perm -> string -> unit
23+
1924
(** [save_as filename ?mode f] is similar to
20-
[Control.with_open_file_bin] except that writing is done to a
21-
temporary file that will be renamed to [filename] after [f] has
22-
succesfully terminated. Therefore this guarantee that either
23-
[filename] will not be modified or will contain whatever [f] was
24-
writing to it as a side-effect.
25+
[Control.with_open_file_bin] for regular files, except that
26+
writing is done to a temporary file that will be renamed to
27+
[filename] after [f] has succesfully terminated. Therefore this
28+
guarantee that either [filename] will not be modified or will
29+
contain whatever [f] was writing to it as a side-effect.
30+
31+
There is no such special treatment for special files (Unix.stat
32+
kind not S_REG, e.g. devices, pipes, etc), instead they are
33+
written to directly. Symlinks are followed (not overwritten in
34+
place). Throws {!Unix.Unix_error} on broken symlinks.
2535
2636
FIXME windows *)
2737
val save_as : string -> ?mode:Unix.file_perm -> (out_channel -> unit) -> unit

httpev.ml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ let finish ?(shutdown=true) c =
284284
| Ready req ->
285285
Hashtbl.remove c.server.reqs req.id;
286286
if c.server.config.debug then
287-
log #info "finished %s" (show_request req)
287+
log #info "finished %s" (show_request req) ~structured_pairs:(pairs_of_request req)
288288

289289
let write_f c (data,ack) ev fd _flags =
290290
let finish () = finish c; Ev.del ev in
@@ -324,7 +324,7 @@ let log_access_apache ch code size ?(background=false) req =
324324
(header_safe req "x-request-id")
325325
(if background then " (BG)" else "")
326326
with exn ->
327-
log #warn ~exn "access log : %s" @@ show_request req
327+
log #warn ~exn "access log : %s" (show_request req) ~structured_pairs:(pairs_of_request req)
328328

329329
let log_status_apache ch status size req =
330330
match status with
@@ -498,10 +498,10 @@ let handle_request c body answer =
498498
| `Ok -> answer c.server req k
499499
end
500500
| _ ->
501-
log #info "version %u.%u not supported from %s" (fst req.version) (snd req.version) (show_request req);
501+
log #info "version %u.%u not supported from %s" (fst req.version) (snd req.version) (show_request req) ~structured_pairs:(pairs_of_request req);
502502
send_reply_async c Identity (`Version_not_supported,[],"HTTP/1.0 is supported")
503503
with exn ->
504-
log #error ~exn "answer %s" @@ show_request req;
504+
log #error ~exn "answer %s" (show_request req) ~structured_pairs:(pairs_of_request req);
505505
match req.blocking with
506506
| None -> send_reply_async c Identity (`Not_found,[],"Not found")
507507
| Some _ -> Exn.suppress teardown c.fd
@@ -639,7 +639,7 @@ let check_hung_requests server =
639639
let now = Time.now () in
640640
server.reqs |> Hashtbl.iter begin fun _ req ->
641641
if req.recv -. now > Time.minutes 30 then
642-
log #warn "request takes too much time to process : %s" (show_request req)
642+
log #warn "request takes too much time to process : %s" (show_request req) ~structured_pairs:(pairs_of_request req)
643643
end
644644

645645
let check_waiting_requests srv =
@@ -845,7 +845,7 @@ let answer_blocking ?(debug=false) srv req answer k =
845845
| Continue continue -> 200, Some continue
846846
| exn ->
847847
let saved_backtrace = Exn.get_backtrace () in
848-
log #warn ~exn ~backtrace:debug ~saved_backtrace "answer forked %s" (show_request req);
848+
log #warn ~exn ~backtrace:debug ~saved_backtrace "answer forked %s" (show_request req) ~structured_pairs:(pairs_of_request req);
849849
-1, None
850850
in
851851
if srv.config.access_log_enabled then
@@ -873,7 +873,7 @@ let answer_forked ?debug srv req answer k =
873873
end;
874874
U.sys_exit 0
875875
| `Forked pid ->
876-
log #info "forked %d : %s" pid (show_request req);
876+
log #info "forked %d : %s" pid (show_request req) ~structured_pairs:(pairs_of_request req);
877877
k (`No_reply,[],""); (* close socket in parent immediately *)
878878
Hashtbl.add srv.h_childs pid ()
879879
end
@@ -883,7 +883,7 @@ let answer_forked ?debug srv req answer k =
883883
do_fork ()
884884
with
885885
exn ->
886-
log #warn ~exn "answer fork failed %s" (show_request req);
886+
log #warn ~exn "answer fork failed %s" (show_request req) ~structured_pairs:(pairs_of_request req);
887887
k (`Internal_server_error,[],"")
888888
in
889889
if Hashtbl.length srv.h_childs < srv.config.max_data_childs then
@@ -899,7 +899,7 @@ let answer_forked ?debug srv req answer k =
899899
else
900900
begin
901901
incr nr_rejected;
902-
log #info "rejecting, overloaded : %s" (show_request req);
902+
log #info "rejecting, overloaded : %s" (show_request req) ~structured_pairs:(pairs_of_request req);
903903
k (`Service_unavailable, ["Content-Type", "text/plain"], "overloaded")
904904
end
905905

@@ -989,11 +989,11 @@ let handle_request_lwt c req answer =
989989
try%lwt
990990
answer c.server req
991991
with exn ->
992-
log #error ~exn "answer %s" @@ show_request req;
992+
log #error ~exn "answer %s" (show_request req) ~structured_pairs:(pairs_of_request req);
993993
return (`Not_found,[],"Not found")
994994
end
995995
| _ ->
996-
log #info "version %u.%u not supported from %s" (fst req.version) (snd req.version) (show_request req);
996+
log #info "version %u.%u not supported from %s" (fst req.version) (snd req.version) (show_request req) ~structured_pairs:(pairs_of_request req);
997997
return (`Version_not_supported,[],"HTTP/1.0 is supported")
998998

999999
let read_buf ic buf =
@@ -1163,7 +1163,7 @@ let rest ~show_exn req answer =
11631163
| Arg.Bad s -> bad_request @@ sprintf "bad parameter %s in %s" s req.url
11641164
| exn ->
11651165
let ref = random_ref () in
1166-
log#warn ~exn "failed ref:%Ld %s" ref (show_request req);
1166+
log#warn ~exn "failed ref:%Ld %s" ref (show_request req) ~structured_pairs:(pairs_of_request req);
11671167
if show_exn then
11681168
internal_error @@ sprintf "internal error ref:%Ld : %s" ref (match exn with Failure s -> s | _ -> Exn.str exn)
11691169
else

httpev_common.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ let show_request req =
106106
(header_safe req "user-agent")
107107
(header_safe req "x-request-id")
108108

109+
let pairs_of_request req : Logger.Pairs.t =
110+
[ "req_id", string_of_int req.id;
111+
"client_addr", show_client_addr req;
112+
"http_duration", sprintf "%.4f" (Time.get () -. req.conn);
113+
"http_recv_duration", sprintf "%.4f" (req.recv -. req.conn);
114+
"http_host", header_safe req "host";
115+
"url", req.url;
116+
"http_user_agent", header_safe req "user-agent";
117+
"http_req_id", header_safe req "x-request-id"
118+
]
119+
109120
let status_code : reply_status -> int = function
110121
| `Ok -> 200
111122
| `Created -> 201

0 commit comments

Comments
 (0)