blob: 999acf03f66af7a46d515dbd0d7db610f65e0cb5 [file] [log] [blame]
Bram Moolenaar4c5678f2022-11-30 18:12:19 +00001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 */
8
9/*
10 * Implements logging. Originally intended for the channel feature, which is
11 * why the "ch_" prefix is used. Also useful for any kind of low-level and
Dominique Pelleb49dfd02023-04-14 21:54:25 +010012 * async debugging.
Bram Moolenaar4c5678f2022-11-30 18:12:19 +000013 */
14
15#include "vim.h"
16
17#if defined(FEAT_EVAL) || defined(PROTO)
18
19// Log file opened with ch_logfile().
20static FILE *log_fd = NULL;
21static char_u *log_name = NULL;
22#ifdef FEAT_RELTIME
23static proftime_T log_start;
24#endif
25
26 void
27ch_logfile(char_u *fname, char_u *opt)
28{
29 FILE *file = NULL;
30 char *mode = "a";
31
32 if (log_fd != NULL)
33 {
34 if (*fname != NUL)
35 ch_log(NULL, "closing this logfile, opening %s", fname);
36 else
37 ch_log(NULL, "closing logfile %s", log_name);
38 fclose(log_fd);
39 }
40
41 // The "a" flag overrules the "w" flag.
42 if (vim_strchr(opt, 'a') == NULL && vim_strchr(opt, 'w') != NULL)
43 mode = "w";
44 ch_log_output = vim_strchr(opt, 'o') != NULL ? LOG_ALWAYS : FALSE;
45
46 if (*fname != NUL)
47 {
48 file = fopen((char *)fname, mode);
49 if (file == NULL)
50 {
51 semsg(_(e_cant_open_file_str), fname);
52 return;
53 }
54 vim_free(log_name);
55 log_name = vim_strsave(fname);
56 }
57 log_fd = file;
58
59 if (log_fd != NULL)
60 {
61 fprintf(log_fd, "==== start log session %s ====\n",
62 get_ctime(time(NULL), FALSE));
63 // flush now, if fork/exec follows it could be written twice
64 fflush(log_fd);
65#ifdef FEAT_RELTIME
66 profile_start(&log_start);
67#endif
68 }
69}
70
71 int
72ch_log_active(void)
73{
74 return log_fd != NULL;
75}
76
77 static void
Bram Moolenaar3b8c7082022-11-30 20:20:56 +000078ch_log_lead(const char *what, channel_T *ch UNUSED, ch_part_T part UNUSED)
Bram Moolenaar4c5678f2022-11-30 18:12:19 +000079{
80 if (log_fd == NULL)
81 return;
82
83#ifdef FEAT_RELTIME
84 proftime_T log_now;
85
86 profile_start(&log_now);
87 profile_sub(&log_now, &log_start);
88 fprintf(log_fd, "%s ", profile_msg(&log_now));
89#endif
90#ifdef FEAT_JOB_CHANNEL
91 if (ch != NULL)
92 {
93 if (part < PART_COUNT)
94 fprintf(log_fd, "%son %d(%s): ", what, ch->ch_id, ch_part_names[part]);
95 else
96 fprintf(log_fd, "%son %d: ", what, ch->ch_id);
97 }
98 else
99#endif
100 fprintf(log_fd, "%s: ", what);
101}
102
103#ifndef PROTO // prototype is in proto.h
104 void
105ch_log(channel_T *ch, const char *fmt, ...)
106{
107 if (log_fd == NULL)
108 return;
109
110 va_list ap;
111
112 ch_log_lead("", ch, PART_COUNT);
113 va_start(ap, fmt);
114 vfprintf(log_fd, fmt, ap);
115 va_end(ap);
116 fputc('\n', log_fd);
117 fflush(log_fd);
118 did_repeated_msg = 0;
119}
120
121 void
122ch_error(channel_T *ch, const char *fmt, ...)
123{
124 if (log_fd == NULL)
125 return;
126
127 va_list ap;
128
129 ch_log_lead("ERR ", ch, PART_COUNT);
130 va_start(ap, fmt);
131 vfprintf(log_fd, fmt, ap);
132 va_end(ap);
133 fputc('\n', log_fd);
134 fflush(log_fd);
135 did_repeated_msg = 0;
136}
137#endif
138
139#if defined(FEAT_JOB_CHANNEL) || defined(PROTO)
140/*
141 * Log a message "buf[len]" for channel "ch" part "part".
142 * Only to be called when ch_log_active() returns TRUE.
143 */
144 void
145ch_log_literal(
146 char *lead,
147 channel_T *ch,
148 ch_part_T part,
149 char_u *buf,
150 int len)
151{
152 ch_log_lead(lead, ch, part);
153 fprintf(log_fd, "'");
154 vim_ignored = (int)fwrite(buf, len, 1, log_fd);
155 fprintf(log_fd, "'\n");
156 fflush(log_fd);
157}
158#endif
159
160/*
161 * "ch_log()" function
162 */
163 void
164f_ch_log(typval_T *argvars, typval_T *rettv UNUSED)
165{
166 char_u *msg;
167 channel_T *channel = NULL;
168
169 if (in_vim9script()
170 && (check_for_string_arg(argvars, 0) == FAIL
171 || check_for_opt_chan_or_job_arg(argvars, 1) == FAIL))
172 return;
173
174 msg = tv_get_string(&argvars[0]);
175#if defined(FEAT_JOB_CHANNEL)
176 if (argvars[1].v_type != VAR_UNKNOWN)
177 channel = get_channel_arg(&argvars[1], FALSE, FALSE, 0);
178#endif
179
Bram Moolenaar4f501172022-12-01 11:02:23 +0000180 // Prepend "ch_log()" to make it easier to find these entries in the
181 // logfile.
182 ch_log(channel, "ch_log(): %s", msg);
Bram Moolenaar4c5678f2022-11-30 18:12:19 +0000183}
184
185/*
186 * "ch_logfile()" function
187 */
188 void
189f_ch_logfile(typval_T *argvars, typval_T *rettv UNUSED)
190{
191 char_u *fname;
192 char_u *opt = (char_u *)"";
193 char_u buf[NUMBUFLEN];
194
195 // Don't open a file in restricted mode.
196 if (check_restricted() || check_secure())
197 return;
198
199 if (in_vim9script()
200 && (check_for_string_arg(argvars, 0) == FAIL
201 || check_for_opt_string_arg(argvars, 1) == FAIL))
202 return;
203
204 fname = tv_get_string(&argvars[0]);
205 if (argvars[1].v_type == VAR_STRING)
206 opt = tv_get_string_buf(&argvars[1], buf);
207 ch_logfile(fname, opt);
208}
209
210#endif // FEAT_EVAL