[Libvir] [PATCH] output virsh log to file

Daniel Veillard veillard at redhat.com
Fri Jun 1 13:41:56 UTC 2007


On Fri, Jun 01, 2007 at 07:35:00PM +0900, Nobuhiro Itou wrote:
> Hi, Daniel

  Hi Nobuhiro,

> >   But since the patch is relatively simple based on existing virsh logging
> > code, I think this could go as a command line option for virsh, for example
> >    --log filename
> > where the detailed logs can then be saved if needed when a problem occurs.
> > I think this would avoid the main drawbacks of your proposed patch.
> 
> I agree about a command line option.
> So, I remaked the patch which --log option is added for virsh.
> How about this one?

 I guess you still think of a single log file shared by multiple virsh run,
and honnestly I think this adds way too much complexity and is not really
useful. You have one log file per virsh run. It's the responsability of the
user to pick a log file name. Trying to reinvent syslog at the level of virsh
does not sounds right to me, or rather it makes what should be a small patch
something really complex instead, I am not sure it is worth it.

> Index: src/virsh.c
[...]
> +#define LOCK_NAME     ".log.lck"
> +#define LOCK_TIMER    100  /* mili seconds */

  Honnestly I don't see the need for a lock. The filename is provided by
the user, I would not add this extra layer there.

> + * Indicates the level of an log massage

  typo, message :-)

> +/*
> + * vshLogDef - log definition
> + */
> +typedef struct {
> +    int log_fd;   /* log file descriptor */
> +    int lock_fd;  /* lock file descriptor */
> +} vshLogDef;

  Drop the lock, then you just have the fd, then you don't need a structure
and everything is way simpler.

[...]
>      va_start(ap, format);
> -    vfprintf(stdout, format, ap);
> +    if (level <= ctl->debug)
> +        vfprintf(stdout, format, ap);
> +    vshOutputLogFile(ctl, VSH_ERR_DEBUG, format, ap);
>      va_end(ap);
>  }

  I am not 100% sure you can actually reuse ap again without doing the
va_end(ap) and va_start(ap, format) again. This may work in some case but
break on some implemntations.

> @@ -3274,6 +3321,7 @@ vshError(vshControl * ctl, int doexit, c
>  
>      va_start(ap, format);
>      vfprintf(stderr, format, ap);
> +    vshOutputLogFile(ctl, VSH_ERR_ERROR, format, ap);
>      va_end(ap);

  same here

[...]
> +    if (ctl->logfile[0] != '/') {
> +        vshError(ctl, TRUE, _("the log file name has to be full path"));
> +    }

 hum, I don't see why you would prevent relative paths for log files
from a virsh run, except it makes simpler to compute the path to the 
lock file, but agains let's just get rid of that lock.

> +    if (ctl->logfile[strlen(ctl->logfile) - 1] == '/') {
> +        vshError(ctl, TRUE, _("the log path is not a file name"));
> +    }

  I would not do that kind of test. Open the file for writing, if it fails 
emit an error and return.

> +    do {
> +        /* check log directory */
> +        snprintf(file_buf, sizeof(file_buf), "%s", ctl->logfile);
> +        snprintf(path_buf, sizeof(path_buf), "%s", dirname(file_buf));
> +        while ((ret = stat(path_buf, &st)) == -1) {
> +            switch (errno) {
> +                case ENOENT:
> +                    if (mkdir(path_buf, DIR_MODE) == -1) {
> +                        switch (errno) {
> +                            case ENOENT:
> +                                snprintf(file_buf, sizeof(file_buf), "%s", path_buf);
> +                                snprintf(path_buf, sizeof(path_buf), "%s", dirname(file_buf));
> +                                continue;
> +                            default:
> +                                vshError(ctl, TRUE, _("failed to create the log directory"));
> +                                break;
> +                        }
> +                    }
> +                    break;
> +                default:
> +                    vshError(ctl, TRUE, _("failed to get the log directory information"));
> +                    break;
> +            }
> +            break;
> +        }
> +        if (ret == 0 && !S_ISDIR(st.st_mode)) {
> +            vshError(ctl, TRUE, _("no such the log directory"));
> +        }
> +    } while (ret == -1);

   This is way too complex. I certainly do not want to create directories
just because an error was made typing the path to the log file. This doesn't
make much sense to me.
   Try to open the file for writing, if it fails, error out, that's all.

> +    /* check log file */
> +    if (stat(ctl->logfile, &st) == -1) {
> +        switch (errno) {
> +            case ENOENT:
> +                break;
> +            default:
> +                vshError(ctl, TRUE, _("failed to get the log file information"));
> +                break;
> +        }
> +    } else {
> +        if (!S_ISREG(st.st_mode)) {
> +            vshError(ctl, TRUE, _("the log path is not a file"));
> +        }
> +    }

  That could be kept but IMHO this is still more than needed.

> +    /* lock file open */
> +    snprintf(file_buf, sizeof(file_buf), "%s/%s", path_buf, LOCK_NAME);
> +    if ((logdef.lock_fd = open(file_buf, O_WRONLY | O_CREAT, LOCK_MODE)) < 0) {
> +        vshError(ctl, TRUE, _("failed to open the log lock file"));
> +    }
> +    /* log file open */
> +    if ((logdef.log_fd = open(ctl->logfile, O_WRONLY | O_APPEND | O_CREAT | O_SYNC, FILE_MODE)) < 0) {
> +        vshError(ctl, TRUE, _("failed to open the log file"));
> +    }
> +}

  Please drop the lock. Locking would have been needed if virsh required
using the same file names. Now the user provides the file name, it's his
responsability to get it right.

[...]
> +
> +    if (logdef.lock_fd == -1 || logdef.log_fd == -1)
> +        return;
> +
> +    /* lock */

drop locking. Anyway intermixed output from multiple run is unlikely to be very
helpful, actually more a source of confusion than help in my optinion.

[...]
> +    /**
> +     * create log format
> +     *
> +     * [YYYY.MM.DD HH:MM:SS SIGNATURE PID] LOG_LEVEL message
> +    */
> +    gettimeofday(&stTimeval, NULL);
> +    stTm = localtime(&stTimeval.tv_sec);
> +    snprintf(msg_buf, sizeof(msg_buf),
> +             "[%d.%02d.%02d %02d:%02d:%02d ",
> +             (1900 + stTm->tm_year),
> +             (1 + stTm->tm_mon),
> +             (stTm->tm_mday),
> +             (stTm->tm_hour),
> +             (stTm->tm_min),
> +             (stTm->tm_sec));
> +    snprintf(msg_buf + strlen(msg_buf), sizeof(msg_buf) - strlen(msg_buf),
> +             "%s %d] ", SIGN_NAME, getpid());

saving the time is okay. I don't think sharing log files for multiple run
helps in any way, so no need to put the pid in...

[...]
> +    /* Check log size */
> +    if (stat(ctl->logfile, &st) == 0) {
> +        if (st.st_size >= MAX_LOGSIZE) {
> +            /* rotate logs */
> +            for (i = ROTATE_NUM - 1; i >= 1; i--) {
> +                snprintf(bak_old_path, sizeof(bak_old_path), "%s.%d", ctl->logfile, i);
> +                snprintf(bak_new_path, sizeof(bak_new_path), "%s.%d", ctl->logfile, i+1);
> +                if (rename(bak_old_path, bak_new_path) == -1) {
> +                    switch (errno) {
> +                        case ENOENT:
> +                            break;
> +                        default:
> +                            /* unlock file */
> +                            flk.l_type = F_UNLCK;
> +                            fcntl(logdef.lock_fd, F_SETLK, &flk);
> +                            vshCloseLogFile();
> +                            vshError(ctl, FALSE, _("failed to rename the log file"));
> +                            return;
> +                    }
> +                }
> +            }
> +            snprintf(bak_new_path, sizeof(bak_new_path), "%s.%d", ctl->logfile, 1);
> +            if (rename(ctl->logfile, bak_new_path) == -1) {
> +                /* unlock file */
> +                flk.l_type = F_UNLCK;
> +                fcntl(logdef.lock_fd, F_SETLK, &flk);
> +                vshCloseLogFile();
> +                vshError(ctl, FALSE, _("failed to rename the log file"));
> +                return;
> +            }
> +        }
> +    }

  I would not bother with the log size either. Just dump to the file. If the
output is big well the user will probably need all of it to sort out the 
problem, but basically it shouldn't grow very large if it is not shared between
virsh runs.

  I expect the use to be the following:
     - users uses virsh for virtualization operation
     - something suddenly does not work
     - then he re-runs the command with logging 
     - then he can analyze the log or transmit it to a sysadmin who
       can have a look
but I don't believe in reimplementing something like syslog within virsh to 
log all operations all the time, especially with a fixed size buffer. logs 
will be intermixed, hard to process, add a burden on the server, and makes
the code way more complex than it needs to be.

  Maybe I didn't understood how you expected logging to work, but apparently
we had different viewpoints, I would rather go for the simplest,

  does that still work for your use case ?

Daniel

-- 
Red Hat Virtualization group http://redhat.com/virtualization/
Daniel Veillard      | virtualization library  http://libvirt.org/
veillard at redhat.com  | libxml GNOME XML XSLT toolkit  http://xmlsoft.org/
http://veillard.com/ | Rpmfind RPM search engine  http://rpmfind.net/




More information about the libvir-list mailing list