Усечение файла во время его использования (Linux)

У меня есть процесс, который записывает много данных в стандартный вывод, который я перенаправляю в файл журнала. Я хотел бы ограничить размер файла, периодически копируя текущий файл с новым именем и обрезая его.

Мои обычные методы усечения файла, такие как

cp /dev/null file

не работает, предположительно, потому что процесс использует его.

Есть ли способ урезать файл? Или удалить его и как-то связать стандартный вывод процесса с новым файлом?

FWIW, это сторонний продукт, который я не могу изменить, чтобы изменить его модель ведения журнала.

ПеренаправлениеEDIT на файл, похоже, имеет ту же проблему, что и копия выше - файл возвращается к своему предыдущему размеру при следующей записи:

ls -l sample.log ; echo > sample.log ; ls -l sample.log ; sleep 10 ; ls -l sample.log
-rw-rw-r-- 1 user group 1291999 Jun 11  2009 sample.log
-rw-rw-r-- 1 user group 1 Jun 11  2009 sample.log
-rw-rw-r-- 1 user group 1292311 Jun 11  2009 sample.log

13 ответов

Решение

Посмотрите на утилиту split(1), часть GNU Coreutils.

Начиная с Coreutils 7.0, есть truncate команда.

Интересная вещь в этих восстановленных файлах состоит в том, что первые 128 КБ или около того будут все нули после того, как вы обрежете файл путем копирования /dev/null над ним. Это происходит потому, что файл усекается до нулевой длины, но дескриптор файла в приложении по-прежнему указывает сразу после последней записи. Когда он снова записывает, файловая система рассматривает начало файла как все нулевые байты - без фактической записи нулей на диск.

В идеале вы должны попросить поставщика приложения открыть файл журнала с O_APPEND флаг. Это означает, что после того, как вы урежете файл, следующая запись будет неявно искать конец файла (то есть вернуться к нулевому смещению), а затем записывать новую информацию.


Этот код содержит стандартный вывод, поэтому он находится в O_APPEND режим, а затем вызывает команду, заданную его аргументами (скорее как nice запускает команду после настройки ее nice-level, или nohup запускает команду после исправления, игнорируя SIGHUP).

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>

static char *arg0 = "<unknown>";

static void error(const char *fmt, ...)
{
    va_list args;
    int errnum = errno;
    fprintf(stderr, "%s: ", arg0);
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
    putc('\n', stderr);
    fflush(0);
    exit(1);
}

int main(int argc, char **argv)
{
    int attr;
    arg0 = argv[0];

    if (argc < 2)
        error("Usage: %s cmd [arg ...]", arg0);
    if ((attr = fcntl(1, F_GETFL, &attr)) < 0)
        error("fcntl(F_GETFL) failed");
    attr |= O_APPEND;
    if (fcntl(1, F_SETFL, attr) != 0)
        error("fcntl(F_SETFL) failed");
    execvp(argv[1], &argv[1]);
    error("failed to exec %s", argv[1]);
    return(1);
}

Мои тесты были несколько случайными, но едва достаточными, чтобы убедить меня, что это работает.


Более простая альтернатива

Billy отмечает в своем ответе:>>'является оператором добавления - и действительно, в Solaris 10 bash (версия 3.00.16(1)) действительно использует O_APPEND flag - тем самым делая ненужный код выше, как показано ("Black JL:" - моя подсказка на этом компьютере):

Black JL: truss -o bash.truss bash -c "echo Hi >> x3.29"
Black JL: grep open bash.truss
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
open("/usr/lib/libcurses.so.1", O_RDONLY)       = 3
open("/usr/lib/libsocket.so.1", O_RDONLY)       = 3
open("/usr/lib/libnsl.so.1", O_RDONLY)          = 3
open("/usr/lib/libdl.so.1", O_RDONLY)           = 3
open("/usr/lib/libc.so.1", O_RDONLY)            = 3
open("/platform/SUNW,Ultra-4/lib/libc_psr.so.1", O_RDONLY) = 3
open64("/dev/tty", O_RDWR|O_NONBLOCK)           = 3
stat64("/usr/openssl/v0.9.8e/bin/bash", 0xFFBFF2A8) Err#2 ENOENT
open64("x3.29", O_WRONLY|O_APPEND|O_CREAT, 0666) = 3
Black JL:

Используйте добавление перенаправления, а не код оболочки (' cantrip') выше. Это просто показывает, что когда вы используете одну конкретную технику для других (допустимых) целей, адаптация ее к другой не обязательно является самым простым механизмом, даже если он работает.

Перенаправьте вывод, используя >> вместо>. Это позволит вам обрезать файл без возврата его исходного размера. Кроме того, не забудьте перенаправить STDERR (2>&1).

Таким образом, конечный результат будет: myprogram >> myprogram.log 2>&1 &

Пытаться > file,


Обновление относительно комментариев: это работает хорошо для меня:

robert@rm:~> echo "content" > test-file
robert@rm:~> cat test-file 
content
robert@rm:~> > test-file
robert@rm:~> cat test-file 

У меня была похожая проблема на Redhat v6, echo > file или же > file вызывал сбой apache и tomcat, поскольку файлы журналов становились для них недоступными.

И исправление было странным

echo " " > file

очистит файл и не вызовет никаких проблем.

В Linux (фактически все unicies) файлы создаются, когда они открываются и удаляются, когда ничто не содержит ссылки на них. В этом случае программа, которая открыла его, и каталог, в котором он был открыт, содержат ссылки на файл. Когда программа cp хочет выполнить запись в файл, она получает ссылку на него из каталога, записывает нулевую длину в метаданные, хранящиеся в каталоге (это небольшое упрощение), и отказывается от дескриптора. Затем исходная программа, по-прежнему сохраняющая оригинальный дескриптор файла, записывает в файл дополнительные данные и сохраняет то, что, по ее мнению, должна быть длина.

даже если вы удалите файл из каталога, программа продолжит записывать в него данные (и использовать место на диске), даже если ни одна другая программа не сможет ссылаться на него.

Короче говоря, когда программа имеет ссылку (дескриптор) на файл, вы ничего не измените.

теоретически есть способы изменить поведение программ, установив LD_LIBRARY_PATH для включения программы, которая перехватывает все системные вызовы доступа к файлам. Я помню, что видел где-то подобное, хотя не могу вспомнить имя.

Поскольку файл используется, если вы попытаетесь аннулировать его или что-то в этом роде, иногда это может "запутать" приложение, записывающее файл журнала, и после этого может ничего не регистрировать.

То, что я попытался бы сделать, это установить своего рода прокси / фильтр для этого журнала, вместо того, чтобы перенаправлять в файл, перенаправлять в процесс или что-то, что получит входные данные и запишет в скользящий файл.

Может быть, это можно сделать с помощью сценария, в противном случае вы можете написать простое приложение для этого (Java или что-то еще). Влияние на производительность приложения должно быть довольно незначительным, но вам придется запустить некоторые тесты.

Кстати, ваше приложение, это автономное веб-приложение,...? Может быть, есть другие варианты для изучения.

Изменить: есть также Оператор добавления перенаправления >>, который я лично никогда не использовал, но он может не заблокировать файл.

Я скачал и скомпилировал последнюю coreutils чтобы я мог иметь truncate имеется в наличии.

Ран ./configure а также make, но не побежал make install,

Все скомпилированные утилиты появляются в папке "src".

Я побежал

[path]/src/truncate -s 1024000 textfileineedtotruncate.log

на 1,7 ГБ файла журнала.

Это не изменило размер, указанный при использовании ls -l, но это освободило все дисковое пространство - это то, что мне действительно нужно было сделать раньше /var заполнил и убил процесс.

Спасибо за совет по "усечению"!

Вместо того, чтобы перенаправлять его в файл, вы можете передать его программе, которая автоматически поворачивает файл, закрывая его, перемещая и открывая новый каждый раз, когда он становится слишком большим.

Вы проверили поведение любых сигналов, таких как SIGHUP, на сторонний продукт, чтобы увидеть, начнет ли он регистрировать новый файл? Сначала вы должны переместить старый файл на постоянное имя.

убить -HUP [идентификатор процесса]

И тогда это начнёт писать снова.

В качестве альтернативы (как предположил Билли), возможно, перенаправление вывода из приложения в программу регистрации, такую ​​как multilog или ту, которая обычно используется с Apache, известной как cronolog. Тогда у вас будет более точный контроль того, куда все идет, прежде чем оно будет записано в тот начальный дескриптор файла (файл), который на самом деле и все.

@Hobo использует freopen (), он использует поток для открытия файла, указанного в имени файла, или для изменения режима доступа. Если указано новое имя файла, функция сначала пытается закрыть любой файл, уже связанный с потоком (третий параметр), и отключает его. Затем, независимо от того, был ли этот поток успешно закрыт или нет, freopen открывает файл, указанный в имени файла, и связывает его с потоком так же, как fopen будет использовать указанный режим.

если бинарный файл третьей стороны генерирует логи, нам нужно написать оболочку, которая будет вращать логи, а третья сторона будет работать в потоке прокси-сервера, как показано ниже.

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <unistd.h>
#include <string.h>

using namespace std;

extern "C" void * proxyrun(void * pArg){
   static int lsiLineNum = 0;
   while(1) 
   {
     printf("\nLOGGER: %d",++lsiLineNum);
     fflush(stdout);
   }
  return NULL;
}


int main(int argc, char **argv)
{
  pthread_t lThdId;
  if(0 != pthread_create(&lThdId, NULL, proxyrun, NULL))
  {
    return 1;
  }

  char lpcFileName[256] = {0,};

  static int x = 0;

  while(1)
  {
    printf("\n<<<MAIN SLEEP>>>");
    fflush(stdout);
    sprintf(lpcFileName, "/home/yogesh/C++TestPrograms/std.txt%d",++x);
    freopen(lpcFileName,"w",stdout);
    sleep(10);
  }

  return 0;
}

У меня была похожая проблема, и я не смог выполнить "tail -f" на выходе скрипта, который был запущен из cron:

    * * * * * my_script >> /var/log/my_script.log 2>&1

Я исправил это, изменив перенаправление stderr:

    * * * * * my_script >> /var/log/my_script.log 2>/var/log/my_script.err
Другие вопросы по тегам