嵌入式linux下的c语言日志log模块,功能增强(二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 嵌入式linux下的c语言日志log模块,功能增强(二)

日志是应用的镜子,可以发现应用中的问题,重要性不言而喻。


打造一智能日志模块,让运维朝着自动化方向大步迈进。提高效率,降低成本,这也是一种创造利润的途径。


如今网络越来越方便,这个日志组件除了常规的记录日志外,可以对日志分等级显示不同的颜色,支持按日期切割,支持控制文件大小及定时清理若干天的记录文件。更重要的是,一种远程诊断的方案。公司开辟一公共外网ftp服务器,这不费不少钱吧,也放心这服务器占不满空间。现场遇到问题了,只要网络不是问题,就都不是问题,这日志组件可由运维人员去触发机器一下,或让客户手工摁一下,机器主动ftp上去一个日志文件。或者应用里增加自主触发机制,当应用执行到FATAL,严重错误不该出现的地方时,或应用异常崩溃时,主动触发上报日志文件FTP到后台服务器。后台服务器增加监视,发现是致命bug日志,主动推送邮件到开发或运维人员邮箱。开发或运维人员总是盯数据肯定不感兴趣,往往不能及时发现问题,那么这种让问题主动去找你的思路,让你主动的去关注问题,而不是等到问题暴露了才去解决。


这样运维人员还用大热天到处跑吗?还用动不动就抓包?出差?都不用的。只要网络ok,数据跑路的都能实现不用人参与。


那么一个日志模块,什么是你想要的功能?


对我而言,这几点是必须的。


1·.日志分等级记录,可控制等级。


2.不同等级日志显示不同颜色。


3.增加是否启用日志输出到文件开关,可以选择把日志保存到文件中.


4.写文件属于耗时操作,这块要考虑异步写日志,不能阻塞应用或影响应用运行时间效率。


5.按日期生成日志文件,可配置保留多少天,超过设定的天数则自动清除超过天数的日志。


6.可增加参数设定限制日志文件的大小,超过限制大小可选择是从头覆盖还是删除重记。

以下为增强功能,


7.发现应用异常时,不但主动记录日志,而且主动上报异常日志文件。可通过FTP服务自动上报日志到后台FTP服务器,


把问题的发现,变被动为主动。当应用出现不该出现的问题时,主动上报。助力自动化运维。


即只要网络是OK的,不再让用户跑路。不再被动的发现问题。让问题解决于被暴露之前,提高产品的口碑与竞争力。


8.可以由运维人员去简单的触发一下,就把终端的日志通过网络传上去,不用再去找线,不用再去找U盘,不用再去想法把日志拷贝出来再带回电脑上发给开发人员。


9.可以由后台参数控制或比如发送短信,自动控制让某一台终端上报日志。


10.支持日志压缩,压缩为zip或7z等文件,缩小体积,便于储存和通过网络传输。


日志的意义在于排查问题和运维。


若所有机器都把日志上传上去则失去了意义,且服务器也顶不住。而这种被动的触发,针对某个机器的上送是可行的且有价值的。并且还可以在应用中增加当应用出现客户没发现缺不该出现的问题时,主动触发上报异常日志。这为提高产品的稳定性,杜绝问题造成的严重性而未发现提供先机。当某天发现一机器偶然吐出一异常的bug日志时,且这日志暴露的问题若不解决将造成严重后果,而你恰好在你的邮箱里看到,这就在不知不觉中主动发现了问题。不用运维人员去找你,客户去找你,机器向你求救了。那么,救救它吧。


OK,按着这个思想,以下是一个实现,c语言的log模块:


至于FTP部分,文件压缩为zip部分,用go来写,更容易。这也就是为啥用go来开发嵌入式很合适。要是让你用c写一个ftp,你试试?


这就体现了用go开发嵌入式linux的强大之处。用go,简短的几行代码就ok了。且在终端上跑的很溜。


/**
日志打印示例。
使用:
mylog(DEBUG, "This is debug info\n");
结果:
[2018-07-22 23:37:27:172] [DEBUG] [main.cpp:5] This is debug info
默认打印当前时间(精确到毫秒)、文件名称、行号。
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <errno.h>
#include <dirent.h>
#include <stdlib.h>
#include "log.h"
//#ifndef LOGLEVEL
//#define LOGLEVEL DEBUG
//#endif
// 使用了GNU C扩展语法,只在gcc(C语言)生效,
// g++的c++版本编译不通过
static const char* s_loginfo[] = {
    [ERROR] = "ERROR",
    [WARN]  = "WARN",
    [INFO]  = "INFO",
    [DEBUG] = "DEBUG",
};
static char file_names[LOGFILE_MAXCOUNT][LOGFILE_NAMELENTH];
//记录文件名前缀(最好取自终端编号)
static char file_prifix[LOGFILE_NAMELENTH];
//linux消息队列
static int s_msg_id;
static int r_msg_id;
#define MSG_TYPE 1001
#define MAX_TEXT 1024
struct msg_st{
  long int msg_type;
  char text[MAX_TEXT];
};
static pthread_t tid;
//=============================================
static void get_timestamp(char *buffer)
{
    time_t t;
    struct tm *p;
    struct timeval tv;
    int len;
    int millsec;
    t = time(NULL);
    p = localtime(&t);
    gettimeofday(&tv, NULL);
    millsec = (int)(tv.tv_usec / 1000);
    /* 时间格式:[2011-11-15 12:47:34:888] */
    len = snprintf(buffer, 32, "[%04d-%02d-%02d %02d:%02d:%02d:%03d] ",
        p->tm_year+1900, p->tm_mon+1,
        p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, millsec);
    buffer[len] = '\0';
}
//获取当前时间
static int get_curtime( char* outTimeStr )
{
  int ret = 0;
  time_t tTime;
  struct tm *tmTime;
  struct timeval mTime;
  time( &tTime );
  tmTime = localtime( &tTime );
  gettimeofday( &mTime, NULL );
  sprintf( outTimeStr, "%04d%02d%02d%02d%02d%02d",
      tmTime->tm_year + 1900, tmTime->tm_mon + 1,
      tmTime->tm_mday, tmTime->tm_hour,
      tmTime->tm_min, tmTime->tm_sec );
  return ret;
}
//创建文件夹
static int create_dir(const char *sPathName)  
{  
  char dirName[256];  
  strcpy(dirName, sPathName);  
  int i,len = strlen(dirName);
  for(i=1; i<len; i++)  
  {  
    if(dirName[i]=='/')  
    {  
      dirName[i] = 0; 
      if(access(dirName, 0)!=0)  
      {  
        if(mkdir(dirName, 0755)==-1)  
        {   
          fprintf(stderr,"mkdir   error\n");   
          return -1;   
        }  
      }  
      dirName[i] = '/';  
    }  
  }  
  return 0;  
} 
//获取文件大小
static unsigned long get_size(const char *path)
{
  unsigned long filesize = -1;  
  struct stat statbuff;
  if(stat(path, &statbuff) < 0){
    return filesize;
  }else{
    filesize = statbuff.st_size;
  }
  return filesize;
}
//文件是否存在,返回 1文件存在,返回0,文件不存在
static int file_exists(char *filename)
{
  return (access(filename, 0) == 0);
} 
static int read_filelist(char *basePath)
{
  DIR *dir;
  struct dirent *ptr;
  char base[1000];
  int count = 0;
  if ((dir=opendir(basePath)) == NULL)
  {
    fprintf(stderr,"Open dir error...");
    return -1;
  }
  while ((ptr=readdir(dir)) != NULL)
  {
    //printf("count=%d\n",i++);
    if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0)    ///current dir OR parrent dir
      continue;
    else if(ptr->d_type == 8)    ///file
    {
      printf("f_name:%s/%s\n",basePath,ptr->d_name);
      sprintf(file_names[count],"%s",ptr->d_name);
      count++;
      if(count > LOGFILE_MAXCOUNT -1){
        fprintf(stderr,"error!only allow files count=%d\n",LOGFILE_MAXCOUNT);
        return -2;
      }
    }
    else if(ptr->d_type == 10)    ///link file
      printf("l_name:%s/%s\n",basePath,ptr->d_name);
    else if(ptr->d_type == 4)    ///dir
    {
      printf("d_name:%s/%s\n",basePath,ptr->d_name);
      printf("this is a dir\n");
      continue;
      //是目录,继续递归读目录下面的
      //memset(base,'\0',sizeof(base));
      //strcpy(base,basePath);
      //strcat(base,"/");
      //strcat(base,ptr->d_name);
      //read_filelist(base);
    }
  }
  closedir(dir);
  return 0;
}
//处理日志文件是否保留
//原理算法:把日期转换成时间戳,然后由配置的允许保留的天数换算出一个时间范围,
//在遍历日志目录中所有的文件名,提取出日期,在这个时间范围内的保留,否则删除
//关键的地方,算出这个允许保留文件的时间范围,原理是日期转时间戳,再时间戳转日期
static int file_alives_proc()
{
  int ret = 0;
  char curtime[20];  //当前日期时间
  char deadtime[20]; //截止的历史日期
  printf("file_alives_proc:\n");
  get_curtime(curtime);
  //只取日期,省去时间
  memset(&curtime[8],0x30,6);
  printf("ailve maxdays:%d\n",LOGFILE_ALIVEDAYS);
  printf("curtime:%s\n",curtime);
  struct tm* tmp_time = (struct tm*)malloc(sizeof(struct tm));
  //字符串转时间
  strptime(curtime,"%Y%m%d%H%M%S",tmp_time);
  time_t t = mktime(tmp_time);
  printf("t now = %ld\n",t);
  free(tmp_time);  
  time_t t1 = t - LOGFILE_ALIVEDAYS*24*60*60;
  //再把t1转换为时间,即时间戳转时间
  struct tm *p; 
  p = gmtime(&t1); 
  //日期时间转字符串,由于只比较日期,因此忽略时间
  strftime(deadtime, sizeof(deadtime), "%Y%m%d000000", p);  
  printf("deadtime:%s\n",deadtime);
  //以上获取到了curtime和deadtime,有了这个时间范围,接下来就去找这个范围的日志
  //日志文件日期在这个范围内的保留,否则删除
  for(int i = 0; i < LOGFILE_MAXCOUNT; i++ )
  {
    if(strlen(file_names[i]) > 0)
    {
      printf("file_name=%s\n",file_names[i]);
      char ftime[20];
      memset(ftime,0,20);
      memset(ftime,0x30,8);
      //关键,这个截取不能错
      memcpy(ftime,&file_names[i][strlen(LOGFILE_PREFIX)+1+strlen(file_prifix)],8);
      printf("file_time=%s\n",ftime);
      //开始比较 是否在日期范围内
      if(memcmp(ftime,deadtime,8) > 0)
      {
        //大于截止日期的文件保留
        printf("%s------keep alive\n",file_names[i]);
      }else{
        printf("%s----------------dead,need remove!\n",file_names[i]);
        //删除文件
        char dfname[50];
        sprintf(dfname,"%s/%s",LOGFILE_PATH,file_names[i]);
        remove(dfname);
      }
      //
    }else{
      //printf("fname=NULL\n");
    }
  }
  return ret;
}
int open_msg(void) {
  key_t msgkey;
  if ((msgkey = ftok("/tmp", 'a')) < 0) {
    printf("send ftok failed!\n");
    return -1;
  }
  printf("----msgkey is %d\n",msgkey);
  if ((s_msg_id = msgget(msgkey, IPC_CREAT | 0666)) == -1) {
    printf("msgget failed!\n");
    return -1;
  }
  printf("----s_msg_id is %d\n",s_msg_id);
  msgctl(s_msg_id,IPC_RMID,0);//先删除,否则可能满,因其生命周期同内核
  if ((s_msg_id = msgget(msgkey, IPC_CREAT | 0666)) == -1) {
    printf("msgget failed!\n");
    return -1;
  }
  r_msg_id = s_msg_id;
  return 0;
}
static int send_msg(char *buf,int len){
  struct msg_st data;
  data.msg_type = MSG_TYPE;
  strcpy(data.text,buf);
  //s_msg_id = 0;
  if(msgsnd(s_msg_id,&data,len,IPC_NOWAIT) == -1){
    printf("msgsnd failed.\n");
    perror("msgsnd");
    return -1;
  }
  return 0;
}
//若大量连续发的太快,收的太慢,会导致发送失败
static int recv_msg(void)
{
  int rsize;
  struct msg_st data;
  int msgtype = MSG_TYPE;
  char tmpfname[ 50 ];
  char tmpTime[ 14 + 1 ];
  FILE* fp;
  unsigned long filesize;
  memset(data.text,0,sizeof(data.text));
  /*接收消息队列*/    
  //阻塞接收
  rsize = msgrcv(r_msg_id,&data,sizeof(data.text),msgtype,MSG_NOERROR );    
  if(rsize == -1)
  {
    if (errno != ENOMSG) {
      perror("msgrcv");
    }
    printf("No message available for msgrcv()\n");
    return -1;
  }
  else
  {
    //printf("message received: %s\n", data.text);
    get_curtime( tmpTime );
    sprintf( tmpfname, "%s/%s%s_%8.8s.log", LOGFILE_PATH, LOGFILE_PREFIX,file_prifix, tmpTime );
    fp = fopen( tmpfname, "a" );
    if ( NULL == fp )
    {
      fprintf(stderr,"failed to open file,filename=%s\n",tmpfname);
      return -2;
    }
    else
    {
      filesize = get_size(tmpfname);
    //  printf("filesize=%u\n",filesize);
      if((filesize/1024) > LOGFILE_MAXSIZE){
        fprintf(stderr,"failed to write log,only allow maxsize=%ukb,current size=%ukb\n",LOGFILE_MAXSIZE,filesize/1024);
        fclose(fp);
        return -3 ;
      }
      //printf("%s\n",tmpfname);
      fprintf( fp, "%s", data.text );
      fclose(fp);
    }
  }
  return 0;
}
void mylog1(const char* filename, int line, enum LogLevel level, const char* fmt, ...)
{
    if(level > LOGLEVEL)
        return;
    va_list arg_list;
    char buf[1024];
  char sbuf[MAX_TEXT];
    memset(buf, 0, 1024);
  memset(sbuf,0,MAX_TEXT);
    va_start(arg_list, fmt);
    vsnprintf(buf, 1024, fmt, arg_list);
    char time[32] = {0};
    // 去掉*可能*存在的目录路径,只保留文件名
    const char* tmp = strrchr(filename, '/');
    if (!tmp) tmp = filename;
    else tmp++;
    get_timestamp(time);
  switch(level){
    case DEBUG:
      //绿色
      sprintf(sbuf,"\033[1;32m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
    case INFO:
      //蓝色
      sprintf(sbuf,"\033[1;34m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
    case ERROR:
      //红色
      sprintf(sbuf,"\033[1;31m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
    case WARN:
      //黄色
      sprintf(sbuf,"\033[1;33m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
      break;
  }
  printf("%s",sbuf);
#if(LOGFILE_ENABLE)
  //记录日志到文件
  send_msg(sbuf,strlen(sbuf));
#endif
    va_end(arg_list);
}
//考虑了一点儿效率,操作IO比较耗时,那就异步写入吧
//想用单独的线程,负责写日志到文件,自己实现消息队列?
//不这么做了,直接使用linux的消息队列吧
static void* thread_writelog(void* args)
{
  mylog(INFO,"thread_writelog begin...\n");
  while(1)
  {
    recv_msg();
    usleep(20*1000);              
  }
}
//参数prifix,日志文件名前缀,传终端编号
int init_log(char *prifix)
{
  int ret = 0;
  printf("init log:\n");
#if(LOGFILE_ENABLE)
  //检查目录是否存在
  ret = create_dir(LOGFILE_PATH);
  if(0 != ret){
    return ret;
  }
  printf("create dir %s\n success!\n",LOGFILE_PATH);
  ret = read_filelist(LOGFILE_PATH);
  if(0 != ret){
    printf("read_filelist err!,ret =%d\n",ret);
    //return ret;
  }
  //文件名前缀
  if(strlen(prifix) > 0){
    strcpy(file_prifix,prifix);
  }
  //处理是否保留历史记录文件
  file_alives_proc();
  //for(int i = 0; i<30;i++){
  //  printf("%s\n",file_names[i]);
  //}
  //创建消息队列
  ret = open_msg();
  if(0 != ret){
    return ret;
  }
  printf("create msg quene success!\n");
  //创建写日志文件线程
  ret = pthread_create(&tid,
      NULL,
      thread_writelog,
      NULL);
  if(0 != ret)
  {
    fprintf(stderr, "couldn't create thread_writelog, errno %d\n", ret);
  }
  else
  {
    printf("create thread_writelog success!\n");
  }
  printf("init log success!enabled log file...\n");
#else
  printf("init log success!no enable log file...\n");
#endif
  return ret;
}
//
//


/*
//使用demo:
//mylog(DEBUG,"hello world!\n");
//输出:[2019-07-26 14:31:51:882] [DEBUG] comLib.c:1257] hello world!
//
//目前只为个人使用,暂无考虑线程安全,高效率和高并发
//考虑了一点儿效率,写文件操作IO比较耗时,因此日志使用了异步写入,linux消息队列。
//因linux的消息队列,容量和长度有限制,因此若单个消息超1024byte或并发发送几千个消息
//且发送速度很快,大于了队列的接收速度,那么肯定,会发送失败
*/
#ifndef LOG_H_
#define LOG_H_
#ifdef __cplusplus 
extern "C" {
#endif
enum LogLevel
{
    ERROR = 1,
    WARN  = 2,
    INFO  = 3,
    DEBUG = 4,
};
void mylog1(const char* filename, int line, enum LogLevel level, const char* fmt, ...) __attribute__((format(printf,4,5)));
#define mylog(level, format, ...) mylog1(__FILE__, __LINE__, level, format, ## __VA_ARGS__)
//================================================================
//============日志模块操作接口,配置和使用都在下面================
//日志级别定义,小于该级别的日志方能输出
#ifndef LOGLEVEL
#define LOGLEVEL DEBUG
#endif
//目前暂只支持管理目录下的30个日志文件名,文件名长度50以内。大了不处理
#define LOGFILE_MAXCOUNT  30
#define LOGFILE_NAMELENTH 50
//===========日志文件的配置=======================================
//是否启用记录日志到文件功能
#define LOGFILE_ENABLE 1
//日志文件的路径,后面不能带"/"
#define LOGFILE_PATH "/log"
//日志文件名称的前缀
#define LOGFILE_PREFIX "log_b503_"
//日志文件存在的时间 单位(天),会自动删除当前日期-ALIVEDAYS 之前的文件
//限制日志最长保留时间不能超 LOGFILE_MAXCOUNT 天
#define LOGFILE_ALIVEDAYS 7  
//单个日志文件的限制大小 单位kb
#define LOGFILE_MAXSIZE 1*1024 
//================================================================
//日志模块初始化,若要记录日志到文件中,必须init_log且开启LOGFILE_ENABLE
//若不需要记录日志到文件功能,则不要调用init_log();
//调用了也没事,只是别开启LOGFILE_ENABLE
//参数prifix,日志文件名前缀,最好传终端唯一编号
extern int init_log(char *prifix); 
#ifdef __cplusplus 
};
#endif
#


附:


go实现的简易FTP功能,先对日志文件进行zip压缩,然后ftp至后台服务器。


package main
import (
  "archive/zip"
  "flag"
  "fmt"
  "io"
  "log"
  "os"
  "strings"
  "github.com/dutchcoders/goftp"
  "github.com/larspensjo/config"
)
var (
  ftp     *goftp.FTP
  conFile        = flag.String("ftpcfg", "/ftpcfg.ini", "config file")
  Server  string = "127.0.0.1:21"
  User    string = ""
  Pwd     string = ""
)
func checkErr(err error) {
  if err != nil {
    fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
  }
}
/**
@files:需要压缩的文件
@compreFile:压缩之后的文件
*/
func CompressZip(files []*os.File, zipfileName string) (err error) {
  zipfile, err := os.Create(zipfileName)
  if err != nil {
    return err
  }
  defer zipfile.Close()
  zw := zip.NewWriter(zipfile)
  defer zw.Close()
  for _, file := range files {
    err := compressZip(file, zw)
    if err != nil {
      return err
    }
    file.Close()
  }
  return nil
}
/**
功能:压缩文件
@file:压缩文件
@prefix:压缩文件内部的路径
@tw:写入压缩文件的流
*/
func compressZip(file *os.File, zw *zip.Writer) error {
  info, err := file.Stat()
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  // 获取压缩头信息
  head, err := zip.FileInfoHeader(info)
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  // 指定文件压缩方式 默认为 Store 方式 该方式不压缩文件 只是转换为zip保存
  head.Method = zip.Deflate
  fw, err := zw.CreateHeader(head)
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  // 写入文件到压缩包中
  _, err = io.Copy(fw, file)
  file.Close()
  if err != nil {
    log.Println("压缩文件失败:", err.Error())
    return err
  }
  return nil
}
func main() {
  fmt.Println("Hello Go")
  defer func() {
    if r := recover(); r != nil {
      fmt.Printf("crash occurred!capture:%s\n", r)
    }
  }()
  // fmt.Println(os.Args) //打印切片内容
  // for i := 0; i < len(os.Args); i++ {
  //  fmt.Println(os.Args[i])
  // }
  var fname string
  flag.StringVar(&fname, "fname", "", "file name")
  flag.Parse()
  flag.Usage()
  //获取当前路径
  file, _ := os.Getwd()
  cfg, err := config.ReadDefault(file + *conFile)
  checkErr(err)
  //获取配置文件中的配置项
  Server, err = cfg.String("SERVERCONFIG", "Server")
  User, err = cfg.String("USER", "User")
  Pwd, err = cfg.String("USER", "Pwd")
  //压缩ZIP
  var filezips []*os.File
  var filezip *os.File
  if filezip, err = os.Open(fname); err != nil {
    panic(err)
  }
  filezips = append(filezips, filezip)
  fmt.Println("CompressZip:" + fname + ".zip")
  err = CompressZip(filezips, fname+".zip")
  if err != nil {
    panic(err)
  }
  fmt.Println("User:" + User)
  fmt.Println("->begin connect server:" + Server)
  // For debug messages: goftp.ConnectDbg("ftp.server.com:21")
  if ftp, err = goftp.ConnectDbg(Server); err != nil {
    panic(err)
  }
  //ftp.debug = true
  defer ftp.Close()
  fmt.Println("->Successfully connected !!")
  // Username / password authentication
  if err = ftp.Login(User, Pwd); err != nil {
    panic(err)
  }
  fmt.Println("->Login success!")
  if err = ftp.Cwd("/"); err != nil {
    panic(err)
  }
  var curpath string
  if curpath, err = ftp.Pwd(); err != nil {
    panic(err)
  }
  fmt.Printf("Current path: %s\n", curpath)
  // Get directory listing
  // var files []string
  // if files, err = ftp.List(""); err != nil {
  //  panic(err)
  // }
  // fmt.Println("Directory listing:/n", files)
  // Upload a file
  var fileup *os.File
  if fileup, err = os.Open(fname + ".zip"); err != nil {
    panic(err)
  }
  fmt.Println("->begin upload file...")
  fpath := fname + ".zip"
  lastidx := strings.LastIndex(fname+".zip", "/")
  if lastidx != -1 {
    fpath = fname[lastidx:] + ".zip"
  }
  fmt.Printf("fpath:%s\n", fpath)
  if err := ftp.Stor(fpath, fileup); err != nil {
    panic(err)
  }
  fmt.Println("->upload file over!")
  err = os.Remove(fname + ".zip") //上传成功,删除zip文件
  if err != nil {
    fmt.Println("file remove Error!")
    panic(err)
  }
}


编译与使用:


GOOS=linux GOARCH=arm GOARM=7 go build ftp.go
ftp -fname=/log/log_b503_20190730.log
root@b_lcd:/app/opt
./ftp -fname=/log/log_b503_20190730.log
Hello Go
Usage of ./ftp:
  -fname string
        file name
  -ftpcfg string
        config file (default "/ftpcfg.ini")
User:qq8864
->begin connect server:015.3vftp.com:21
2019/07/30 17:44:08 begin receiveLine...
2019/07/30 17:44:09 < 220 Serv-U FTP Server v6.4 for WinSock ready...
2019/07/30 17:44:09 receiveLine over!
2019/07/30 17:44:09 220 Serv-U FTP Server v6.4 for WinSock ready...
->Successfully connected !!
2019/07/30 17:44:09 > USER qq8864
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 331 User name okay, need password.
2019/07/30 17:44:09 receiveLine over!
2019/07/30 17:44:09 > PASS sars1212
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 230 User logged in, proceed.
2019/07/30 17:44:09 receiveLine over!
->Login success!
2019/07/30 17:44:09 > CWD /
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 250 Directory changed to /
2019/07/30 17:44:09 receiveLine over!
2019/07/30 17:44:09 > PWD
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:09 < 257 "/" is current directory.
2019/07/30 17:44:09 receiveLine over!
Current path: /
->begin upload file...
fpath:/log_b503_20190730.log
2019/07/30 17:44:09 > TYPE I
2019/07/30 17:44:09 begin receiveLine...
2019/07/30 17:44:11 < 200 Type set to I.
2019/07/30 17:44:11 receiveLine over!
2019/07/30 17:44:11 > PASV
2019/07/30 17:44:11 begin receiveLine...
2019/07/30 17:44:11 < 227 Entering Passive Mode (52,128,243,181,6,1)
2019/07/30 17:44:11 receiveLine over!
2019/07/30 17:44:11 > STOR /log_b503_20190730.log
2019/07/30 17:44:11 Connecting to 015.3vftp.com:1537
2019/07/30 17:44:11 begin receiveLine...
2019/07/30 17:44:11 < 150 Opening BINARY mode data connection for log_b503_20190730.log.
2019/07/30 17:44:11 receiveLine over!
2019/07/30 17:44:11 a1:150 Opening BINARY mode data connection for log_b503_20190730.log.
2019/07/30 17:44:11 begin receiveLine...
2019/07/30 17:44:13 < 226-Maximum disk quota limited to 102400 kBytes
2019/07/30 17:44:13 receiveLine over!
2019/07/30 17:44:13 begin receiveLine...
2019/07/30 17:44:13 <     Used disk quota 64 kBytes, available 102335 kBytes
2019/07/30 17:44:13 receiveLine over!
2019/07/30 17:44:13 begin receiveLine...
2019/07/30 17:44:13 < 226 Transfer complete.
2019/07/30 17:44:13 receiveLine over!
2019/07/30 17:44:13 a2:226-Maximum disk quota limited to 102400 kBytes
    Used disk quota 64 kBytes, available 102335 kBytes
226 Transfer complete.
->upload file over!
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
279 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
8天前
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
|
1月前
|
人工智能 Java 关系型数据库
Red Hat Enterprise Linux 9.5 发布下载,新增功能亮点概述
Red Hat Enterprise Linux 9.5 发布下载,新增功能亮点概述
73 4
Red Hat Enterprise Linux 9.5 发布下载,新增功能亮点概述
|
18天前
|
存储 监控 安全
什么是事件日志管理系统?事件日志管理系统有哪些用处?
事件日志管理系统是IT安全的重要工具,用于集中收集、分析和解释来自组织IT基础设施各组件的事件日志,如防火墙、路由器、交换机等,帮助提升网络安全、实现主动威胁检测和促进合规性。系统支持多种日志类型,包括Windows事件日志、Syslog日志和应用程序日志,通过实时监测、告警及可视化分析,为企业提供强大的安全保障。然而,实施过程中也面临数据量大、日志管理和分析复杂等挑战。EventLog Analyzer作为一款高效工具,不仅提供实时监测与告警、可视化分析和报告功能,还支持多种合规性报告,帮助企业克服挑战,提升网络安全水平。
|
1月前
|
监控 Linux 开发者
如何在 Linux 中优雅的使用 head 命令,用来看日志简直溜的不行
`head` 命令是 Linux 系统中一个非常实用的工具,用于快速查看文件的开头部分内容。本文介绍了 `head` 命令的基本用法、高级用法、实际应用案例及注意事项,帮助用户高效处理文件和日志,提升工作效率。
33 7
|
1月前
|
存储 监控 安全
什么是日志管理,如何进行日志管理?
日志管理是对IT系统生成的日志数据进行收集、存储、分析和处理的实践,对维护系统健康、确保安全及获取运营智能至关重要。本文介绍了日志管理的基本概念、常见挑战、工具的主要功能及选择解决方案的方法,强调了定义管理目标、日志收集与分析、警报和报告、持续改进等关键步骤,以及如何应对数据量大、安全问题、警报疲劳等挑战,最终实现日志数据的有效管理和利用。
|
2月前
|
监控 网络协议 安全
Linux系统日志管理
Linux系统日志管理
60 3
|
2月前
|
Python
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
42 0
|
2月前
|
数据可视化
Tensorboard可视化学习笔记(一):如何可视化通过网页查看log日志
关于如何使用TensorBoard进行数据可视化的教程,包括TensorBoard的安装、配置环境变量、将数据写入TensorBoard、启动TensorBoard以及如何通过网页查看日志文件。
262 0
|
SQL 数据采集 监控
基于日志服务数据加工分析Java异常日志
采集并脱敏了整个5月份的项目异常日志,准备使用日志服务数据加工做数据清洗以及分析。本案例是基于使用阿里云相关产品(OSS,RDS,SLS等)的SDK展开自身业务。需要对异常日志做解析,将原始日志中时间、错误码、错误信息、状态码、产品信息、请求方法、出错行号提取出来。然后根据提取出来的不同产品信息做多目标分发处理。对清洗后的数据做异常日志数据分析。
818 0
基于日志服务数据加工分析Java异常日志