/**
日志打印示例。
使用:
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:压缩文件内部的路径
为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!
这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**
![](https://img-blog.csdnimg.cn/img_convert/7287bb49ab7f789c40e4bd0005cc3600.png)
本份面试集锦涵盖了
* **174 道运维工程师面试题**
* **128道k8s面试题**
* **108道shell脚本面试题**
* **200道Linux面试题**
* **51道docker面试题**
* **35道Jenkis面试题**
* **78道MongoDB面试题**
* **17道ansible面试题**
* **60道dubbo面试题**
* **53道kafka面试**
* **18道mysql面试题**
* **40道nginx面试题**
* **77道redis面试题**
* **28道zookeeper**
**总计 1000+ 道面试题, 内容 又全含金量又高**
* **174道运维工程师面试题**
> 1、什么是运维?
> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?
> 3、现在给你三百台服务器,你怎么对他们进行管理?
> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点
> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?
> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?
> 7、Tomcat和Resin有什么区别,工作中你怎么选择?
> 8、什么是中间件?什么是jdk?
> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?
> 10、什么叫CDN?
> 11、什么叫网站灰度发布?
> 12、简述DNS进行域名解析的过程?
> 13、RabbitMQ是什么东西?
> 14、讲一下Keepalived的工作原理?
> 15、讲述一下LVS三种模式的工作过程?
> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?
oDB面试题**
* **17道ansible面试题**
* **60道dubbo面试题**
* **53道kafka面试**
* **18道mysql面试题**
* **40道nginx面试题**
* **77道redis面试题**
* **28道zookeeper**
**总计 1000+ 道面试题, 内容 又全含金量又高**
* **174道运维工程师面试题**
> 1、什么是运维?
> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?
> 3、现在给你三百台服务器,你怎么对他们进行管理?
> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点
> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?
> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?
> 7、Tomcat和Resin有什么区别,工作中你怎么选择?
> 8、什么是中间件?什么是jdk?
> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?
> 10、什么叫CDN?
> 11、什么叫网站灰度发布?
> 12、简述DNS进行域名解析的过程?
> 13、RabbitMQ是什么东西?
> 14、讲一下Keepalived的工作原理?
> 15、讲述一下LVS三种模式的工作过程?
> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?
> 17、如何重置mysql root密码?