サイクリックログ

[戻る]
DA 2008/12/19(金) 21:16:58
教えて下さい。

以下でサイクリックログを作ろうとしています。
・ログ出力の度にopen/close
・100件のレコードを出力したら先頭に戻って上書きしていく

fopenを使って"a"モードでopenすれば追記はできるのですが
100件出力したときにfseekで先頭に戻ろうとしてもうまくいきません。
色々と調べたところ、"a"モードではfseekが使えないことがわかりました。

また、"w"では出力済みのログがオープンのたびに消されてしまい
最後のログしか残っていません。

何かいい手段はないかとサイクリックログの手法を探しましたが
手詰まりの状態です。

御助言宜しくお願いします。

2008/12/19(金) 21:35:28
w+


では?

DA 2008/12/21(日) 14:06:54
>悠さん

レスありがとうございます。
w+も試してみましたが、やはり最後のログが残るだけのようです。。。

http://www9.plala.or.jp/sgwr-t/c/sec17.html
こちらの書込みモード別の動作を参考にしているのですが、
wではやはりサイズを0にしてしまうのですよね。
かといって、"a"モードだとシークを動かすことができないし。。。

皆さんはどのようにサイクリックログをされているのでしょうか?

DA 2008/12/21(日) 14:43:37
現状のコードは以下のような感じです。

ログを吐き出す際には、以下の関数を呼んで

LOG(){

DA 2008/12/21(日) 14:52:11
すみません、途中で送信してしまいました。。。

LOG(){

  static int logcnt = 0;

    //ログ内容処理は割愛//
  logcnt += 1;

  fp = fopen("logingtest.log", "a");

  //レコードが10件目にきたら先頭から上書きしたい
  if( (logcnt % 10) == 0){
   fssek(fp, 0, SEEK_SET);
   logcnt = 0;
  }

  fwrite(・・・
  fclose(fp);
}

ryo 2008/12/21(日) 15:39:41
"r+"で

DA 2008/12/21(日) 16:29:29
>ryoさん

ありがとうございます。
"r+"も試してはいたのですがやはりだめです。
最後のログしか出力されていない?
シークが間違ってる気がしますが、いま試行錯誤してます。

最初にopenして、ログ書きっぱなしならサイクリック(シークで)も
可能なのですがそれだと正常終了できたときしかログが残らないんですよね。

あとは、ログを書き込む度に
一旦ファイルを読み込んで、バッファ内で新規ログに結合させて書込み。
ぐらいしか思いつかないのですが、これだと負荷が高い気がします。

subaru 2008/12/21(日) 18:45:41
>"r+"も試してはいたのですがやはりだめです。
>最後のログしか出力されていない?
>シークが間違ってる気がしますが、いま試行錯誤してます。

"r+"で開いた直後にSEEK_ENDにシークで。

たいちう 2008/12/21(日) 20:13:34
サイクリックという仕様を想像してみたのですが、参考になるかな。
解らない点、不足している点があれば再度聞いてください。

#include <stdio.h>
#include <stdlib.h>

void Log()
{
    static int logcnt = 0;
    FILE *fp;
    int ret;
    
    logcnt++;

    if (logcnt > 10) {
        fp = fopen("logingtest.log", "r+");
        ret = fseek(fp, 5 * (logcnt % 10 - 1), SEEK_SET);
    } else {
        fp = fopen("logingtest.log", "a+");
    }
    fprintf(fp, "%3d\n", logcnt);
    fclose(fp);
}

int main()
{
    int i;
    for (i = 0; i < 50; i++) {
        printf("i == %d\n", i);
        Log();
        if (i == 13) {
            // 異常終了
            exit(-1);
        }
    }

    // quit
    printf("End.\n");
    getchar();
    return 0;
}

wood 2008/12/21(日) 22:06:39
最近あまり触っていないものでうろ覚えの私が意見するのも
なんなんですが

>DA 2008/12/21(日) 14:52:11
>
>LOG(){
>
>  static int logcnt = 0;
           ↑
これって、関数に入る度に初期化されませんか?
外部で定義するか、引数にしたほうが良くないですか?

確実な書き込みのために fflush も考慮したほうがよくないでしょうか
途中異常終了しても書き込んでくれますよ

途中異常終了したときは、ストリームバッファに残っている
未物理書き込み情報は破棄されますからね
特に書き込み量(1行あたりが)小さい時は必ずちょん切れます

たいちう 2008/12/21(日) 22:36:21
> これって、関数に入る度に初期化されませんか?
> 外部で定義するか、引数にしたほうが良くないですか?

されませんよ。
関数の中でしか使わず、何回その関数が呼ばれたかのみ数える変数ですので、
私もこのように書くことはあります。

Ban 2008/12/22(月) 06:50:52
> 確実な書き込みのために fflush も考慮したほうがよくないでしょうか

以下のことなら書き込み直後にfcloseしてますのでこの観点でも問題ないと思います。

>    fprintf(fp, "%3d\n", logcnt);
>    fclose(fp);

fcloseは(ファイルを閉じるので当然といえばたぶん当然に)まずフラッシュします。
"stream to be flushed and the associated file to be closed."(ISO/IEC9899)

>  printf("End.\n");
>  getchar();

こっちの話なら分からないこともありませんが、
改行はしてるのでラインバッファになってればおそらく掃けますね。

Ban 2008/12/22(月) 07:07:31
ちなみに、先の例だとexitでもフラッシュされます。

Next, all open streams with unwritten buffered data are flushed,
all open streams are closed,
and all files created by the tmpfile function are removed.

つまり、想定される異常系処理でexitしているのであれば、
途中で途切れていることはないので、特定にも困らないかと。

# ちなみに、本当に異常終了(abnormal program termination)するabortだと
# フラッシュするかは処理系定義なのでコンパイラの仕様次第、
# 落ちバグの類はundefined behaviorなので、そもそも保証なし。

Ban 2008/12/22(月) 07:27:51
# ぱらぱらと連投すみません。

text streamでこの種のfseekするとundefined behavior扱いされるので、
"b"をつけておくべきかと。

>  fp = fopen("logingtest.log", "r+");
>  ret = fseek(fp, 5 * (logcnt % 10 - 1), SEEK_SET);



# こっちなら平気。
# >  fssek(fp, 0, SEEK_SET);

DA 2008/12/22(月) 10:01:02
皆様、御回答ありがとうございます。
いっきにレスが付いていたのでびっくりしました^^;

レスから気付きがあり、少し解決に近づいています。
具体的にlogをどうしたいかというと、10レコードでサイクリックとして

(ログ10件未満の時)※時間は適当
001 22 09:40:35.966 aaa failed
002 22 09:40:35.966 bbbb failed
・・・
010 22 09:40:35.966 cccccc complete

(10件を越えた時)
011 22 09:40:35.966 d complete
012 22 09:40:35.966 ee failed
ed     ←(002 22 09:40:35.966 bbbb failedのごみが残ってもいい)
・・・
010 22 09:40:35.966 cccccc complete

ということで、

    num = strlen(buf);
    numwrited += num;

    //サイクリック開始後は、
    if(logcnt > 10){
        if( (logcnt % 10) == 1 ){
            numwrited = 0;
        }
        //読み書きモードでopen
        fp = fopen(szFileNameBuf, "r+b");
        fseek(fp, numwrited, SEEK_SET);
    }
    //サイクリック開始まではただの追記でopen
    else{
        fp = fopen(szFileNameBuf, "a+b");
    }

    //bufをnumBYTE、一括で書込み
    fwrite(buf, num, 1, fp);
    fclose(fp);

サイクリック開始したら、
シークを先頭から書き込んだ分だけ進めて(これが気づきませんでした)、
そこに出力する手法をとってみたところ、
サイクリックはOK、しかし改行がうまくいかないようになってしまいました。

se025 035 22 09:40:55.247 xxx failed
22036 22 09:40:55.278 xxx fa037 22 09:40:55.293 xxx complete

シークするバイト数と、これまでに書き込んだバイト数の関係が怪しい気がします。

DA 2008/12/22(月) 11:23:22
[[解決]]
解決しました!
サイクリック後のシーク位置が間違ってました。
スキル不足のため冗長なコードでお恥ずかしいですが、
完成後のコードを載せます。
お付き合い頂いた皆様ありがとうございました!

    num = strlen(buf);
    numwrited += num;

    //サイクリック開始後は、
    if(logcnt > 30){
        if( (logcnt % 30) == 1 ){
            numwrited = num;
            fp = fopen(szFileNameBuf, "r+b");
            fseek(fp, 0, SEEK_SET);
        }
        else{
            //読み書きモードでopen
            fp = fopen(szFileNameBuf, "r+b");
            fseek(fp, numwrited-num, SEEK_SET);
        }
    }
    //サイクリック開始まではただの追記でopen
    else{
        fp = fopen(szFileNameBuf, "a+b");
    }

    //bufをnumBYTE、一括で書込み
    fwrite(buf, num, 1, fp);
    fclose(fp);

rin 2008/12/22(月) 12:07:45
>   num = strlen(buf);
>    numwrited += num;      ← これ

これの計算位置がおかしいと思います

numwrited分seekさせるということは、その意味は
「前回書いた場所の最後=今回書き出す位置の先頭位置」
であると思いますが、

処理の前に、
numwrited =「前回書いた場所の最後」 + 「今回書き出す分」
 ||
「今回書き出した終えたときの、最後の位置」
になってる

古いスレッドには追加発言できません。