工作几年,面试还是说不出日志该怎么写更好?——日志规范与最佳实践篇

news2025/1/12 18:06:10

概览

上一篇我们讨论了为什么要使用日志框架,这次我们深入问题的根源,为什么我们需要日志?

大多数开发人员会纠结日志该怎么输出,什么时候输出,输出了会不会有人看等问题,让我们跳出开发人员的局限来考虑这个问题:谁需要日志?日志有几种?日志都需要输出什么?如何输出日志?

谁需要日志?

  • 开发者 开发人员在开发过程中需要输出一些变量方便调试,正确的做法是使用日志来输出(使用 System.out来输出,一不小心发布到线上,会被项目经理痛批);其次线上问题很难重放,用户的表述一般都会失真,况且很多用户发现 bug 就删 app 关网页走人了

  • 运维人员 整个系统大部分时间都是运维人员来维护,日志可以帮助运维人员来了解系统状态(很多运维系统接入的也是日志),运维人员发现日志有异常信息也可以及时通知开发来排查

  • 运营人员 没错,就是运营人员,比如电商的转化率、视频网站的完播率、普通PV数据等都可以通过日志进行统计,随着大数据技术的普及,这部分日志占比也越来越高

  • 安全人员 虽然大多数企业不重视安全,但是安全也可以通过日志来进行预警,比如某个用户突然大额转账、再比如数据库突然出现大量无条件分页查库(拖库)等等

日志有几种?

  • 调试日志 用于开发人员开发调试或者线上回溯问题。

  • 诊断日志 一般用于运维人员监控系统与安全人员分析预警。

  • 埋点日志 一般用于运营决策分析,也有用作微服务调用链路追踪的(运维、调试)。

  • 审计日志 与诊断日志类似,诊断日志偏向运维,审计日志偏向安全。

日志都需要输出什么?

注:日志级别会在下面讲解

  • 调试日志

    • DEBUG 或者 TRACE 级别,比如方法调用参数,网络连接具体信息,一般是开发者调试程序使用,线上非特殊情况关闭这些日志

    • INFO 级别,一般是比较重要却没有风险的信息,如初始化环境、参数,清理环境,定时任务执行,远程调用第一次连接成功

    • WARN 级别,有可能有风险又不影响系统继续执行的错误,比如系统参数配置不正确,用户请求的参数不正确(要输出具体参数方便排查),或者某些耗性能的场景,比如一次请求执行太久、一条sql执行超过两秒,某些第三方调用失败,不太可能被运行的if分支等

    • ERROR 级别,用于程序出错打印堆栈信息,不应该用于输出程序问题之外的其他信息,需要注意打印了日志异常(Exception)就不应该抛(throw)了

  • 诊断日志 一般输出 INFO 级别,请求响应时间,内存占用等等,线上接入监控系统时打开,建议输出到独立的文件,可以考虑 JSON 格式方便外部工具分析

  • 埋点日志 业务按需定制,比如上文提到的转化率可以在用户付款时输出日志,完播率可以在用户播放完成后请求一次后台输出日志,一般可输出 INFO 级别,建议输出到独立的文件,可以考虑JSON格式方便外部工具分析

  • 审计日志 大多 WARN 级别或者 INFO 级别,一般是敏感操作即可输出,登陆、转账付款、授权消权、删除等等,建议输出到独立的文件,可以考虑JSON格式方便外部工具分析

一般调试日志由开发者自定义输出,其他三种应该根据实际业务需求来定制。

日志的其他注意点

  1. 线上日志应尽量谨慎,要思考:这个位置输出日志能帮助排除问题吗?输出的信息与排查问题相关吗?输出的信息足够排除问题吗?做到不少输出必要信息,不多输出无用信息(拖慢系统,淹没有用信息)

  2. 超级 SessionId 与 RequestId,无论是单体应用还是微服务架构,应该为每个用户每次登陆生成一个超级 SessionId,方便跟踪区分一个用户;RequestId,每次请求生成一个 RequestId,用于跟踪一次请求,微服务也可以用于链路追踪

  3. 日志要尽量单行输出,一条日志输出一行,否则不方便阅读以及其他第三方系统或者工具分析

  4. 公司内部应该制定一套通用的日志规范,包括日志的格式,变量名(驼峰、下划线),分隔符(“=”或“:”等),何时输出(比如规定调用第三方前后输出INFO日志),公司的日志规范应该不断优化、调整,找到适合公司业务的最佳规范

OK,理论就聊到这里,接下来让我们回到技术层面。

使用概念

如果要想要学会使用日志框架,先要理解几个简单概念,LoggerAppendersLayout日志级别级别继承(Level Inheritance)

Logger(日志实例)

用于输出日志,调用一次org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)org.slf4j.LoggerFactory#getLogger(java.lang.String)就会产生一个日志实例,相同参数会共用同一个实例。

Appenders

日志输出器,logback 预定义了输出到控制台、文件、Socket 服务器、MySQL、PostgreSQL、Oracle 和其他数据库、JMS 和 UNIX Syslog 系统调用等实现,通过配置文件配置即可使用,当然我们常用的只有控制台和文件两种。

Layout

用于控制日志输出格式,前文所说的”自动输出日志相关信息,如:日期、线程、方法名称等等“就可以用 Layout 来控制,实际使用很简单,写一个 Layout 格式定义表达式(pattern)即可,使用方法类似于Java 的SimpleDateFomat

日志级别

RFC 5424 (page 11)规定了 8 种日志级别,但是SLF4j 只定义了 5 种日志级别,分别是 ERROR、WARN、INFO、DEBUG、TRACE 这五个级别从高到低,配置级别越高日志输出就越少,如下图

我们看到滑动条上五个点正好对应五个级别,滑动指示器可以左右移动,指示器作为分界点,指示器左侧都可以输出,右侧都不能输出,左右调整指示器就可以调整日志的输出,滑倒右侧就可以全部输出,滑倒左侧就可以减少输出,那么是否能够彻底关闭输出呢?答案是可以的,配置文件中还可以配置为 ALL 与 OFF,分别对应所有(等价于TRACE)与关闭。

级别继承

理解了日志级别,让我们来考虑如下场景:

  • 某些重要业务输出 INFO 级别,其他业务输出WARN级别的日志,同时关闭所有库、框架的日志

有需求就会有解决方案,其实很简单,logback 与 log4j 都支持按照日志实例来配置,现在问题解决了,但是新的问题又来了,如果线上所有日志都输出 INFO 级别,难道要一个一个配置吗?这时候就就要请出我们上面所提到的级别继承,如果 Java 一样,logback 与 log4j 中也都是单根继承模型,Java 中是 Object,日志中是 ROOT,如下图:

有了继承机制,我们只需要将 ROOT 调整到 INFO 级别,再按照需求细化调整我们业务对应的 logger 实例级别即可满足绝大多数场景。

Codding 实战

问:把大象装冰箱分几步?分三步:1、引入依赖,2、编码输出日志,3、调整配置文件。前文已经讲过步骤一,如果没有看过的读者请移步公众号查看往期回顾,这里直接进入步骤二。

步骤二

如果项目中使用了Lombok,那么可以直接在类上面加@Slf4j注解既可获得日志实例,否则可以使用static final org.slf4j.Logger logger = LoggerFactory.getLogger(TestLog.class);来获取日志实例

具体日志输出方法如下:

logger.trace("A TRACE Message");logger.debug("A DEBUG Message");logger.info("An INFO Message");logger.warn("A WARN Message");logger.error("An ERROR Message");

这里有个注意点,尽量使用参数占位而不要手动拼接字符串,如下

String level = "Trace";// 反例logger.trace("A " + level + " Message");// 正确的做法logger.trace("A {} Message", level);

这样做可以提高效率,如果不输出日志,第一种情况也会拼接字符串造成性能损耗,第二种就不会有此问题(阿里巴巴Java开发手册(华山版)这里表述有问题,占位符效率更高是因为尽量延迟进行字符串处理,如果不需要输出的日志就不处理了,下一篇原理分析会展开),另外我们也不需要if (logger.isTraceEnabled())来进行判断了(性能损耗不高,但是代码好看多了)。

步骤三

配置文件需要区分 logback 与 log4j2,两种框架在配置文件上有差别但很相似,来看具体配置文件。

logback 配置文件位置

lobback 支持 xml 与 groovy 脚本两种配置方式,logback 查找配置文件位置规则如下(后续文章会讲如何修改位置)

  1. logback 尝试在类路径中找到一个名为 logback-test.xml 的文件。

  2. 如果找不到此类文件,则 logback 会尝试在类路径中找到名为 logback.groovy 的文件。

  3. 如果找不到这样的文件,它将在类路径中检查文件 logback.xml。

  4. 如果找不到此类文件,则通过查找文件 META-INF\services\ch.qos.logback.classic.spi.Configurator,如果有这个文件且内容是 com.qos.logback.classic.spi.Configurator 实现类的全类名,直接加载这个实现类。

  5. 如果以上方法均不能成功执行,则 logback 会使用 BasicConfigurator 自动进行自我配置,会将日志输出到控制台。

这段长长的文字其实不用看,我们就把logback.xml放入Classpath根目录就可以了。。

logback 配置文件编写规则

logback 配置文件主要分为三类,一个或多个 Appender,用于定义输出位置(不同文件位置,或者网络又或者数据库);一个或多个 Logger,用于细化配置不同 logger 的输出级别以及位置;一个 ROOT,是一个特殊的logger,用于配置根 Logger。

我们一起来看下面的配置文件实例​​​​​​​

<?xml version="1.0" encoding="UTF-8"?><configuration scan="false" debug="false">
    <!-- 定义日志文件的存储地 -->    <property name="LOG_PATH" value="/var/log"/>
    <property name="CONSOLE_LOG_PATTERN"              value="%d{HH:mm:ss.SSS} %-5level [%10.10thread] %-30.30logger{29}\(%4L\\) - %msg%n"/>    <!-- 控制台输出 -->    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">        <encoder>            <pattern>${CONSOLE_LOG_PATTERN}</pattern>            <charset>utf-8</charset>        </encoder>    </appender>
    <!-- 文件日志格式(打印日志,不打印行号) -->    <property name="FILE_LOG_PATTERN"              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%10.10thread] %-30.30logger{29} - %msg%n"/>
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">        <!-- 正在记录的日志文件的路径及文件名 -->        <file>${LOG_PATH}/log.log</file>        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">            <!-- yyyy-MM-dd 按日滚动 -->            <fileNamePattern>${LOG_PATH}/log-%d{yyyy-MM-dd}.%i.log</fileNamePattern>            <!-- 单个文件最大50M -->            <maxFileSize>50MB</maxFileSize>            <!--  最多占用5G磁盘空间,500个文件(总共不能超过该5G) -->            <maxHistory>500</maxHistory>            <totalSizeCap>5GB</totalSizeCap>        </rollingPolicy>        <!-- 追加方式记录日志 -->        <append>true</append>        <!-- 日志文件的格式 -->        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">            <pattern>${FILE_LOG_PATTERN}</pattern>            <charset>utf-8</charset>        </encoder>    </appender>
    <!-- 日志输出级别 STDOUT:控制台;FILE_ALL:文件 -->    <root level="warn">        <appender-ref ref="STDOUT"/>    </root>    <logger name="druid.sql" level="warn" additivity="true"/>    <logger name="druid.sql.ResultSet" level="warn" additivity="true"/>    <logger name="com.alibaba.druid.pool.DruidDataSource" level="debug" additivity="true">        <appender-ref ref="FILE_ALL"/>    </logger></configuration>

上面配置文件定义了两个 Appender,一个输出控制台,另一个输出到文件并且自动滚动。需注意的是property标签相当于定义一个变量,可以使用${xxx}进行引用,CONSOLE_LOG_PATTERN 与 FILE_LOG_PATTERN 定义了控制台与文件打印格式,具体编写方式类似于 Java 的SimpleDateFomat就不在此展开了,具体可以参考

  • logback:http://logback.qos.ch/manual/layouts.html

  • log4j:https://logging.apache.org/log4j/2.x/manual/layouts.html

log4j2 配置文件位置

log4j2 支持 XML、JSON、YAML 或者 properties 格式的配置文件,具体查找方式如下:

  1. 检查“log4j.configurationFile”系统属性,如果有,尝试使用与文件扩展名匹配的ConfigurationFactory加载配置。

  2. 如果未设置系统属性,则属性 ConfigurationFactory 将在类路径中查找 log4j2-test.properties。

  3. 如果找不到此类文件,则 YAML ConfigurationFactory将在类路径中查找 log4j2-test.yaml或log4j2-test.yml。

  4. 如果找不到此类文件,则 JSON ConfigurationFactory 将在类路径中查找 log4j2-test.json或log4j2-test.jsn。

  5. 如果找不到这样的文件,XML ConfigurationFactory 将在类路径中查找 log4j2-test.xml。

  6. 如果找不到测试文件,则属性 ConfigurationFactory 将在类路径上查找 log4j2.properties。

  7. 如果找不到属性文件,则 YAML ConfigurationFactory 将在类路径上查找 log4j2.yaml或log4j2.yml。

  8. 如果无法找到 YAML 文件,则 JSON ConfigurationFactory 将在类路径上查找 log4j2.json或log4j2.jsn。

  9. 如果无法找到 JSON 文件,则 XML ConfigurationFactory 将尝试在类路径上找到 log4j2.xml。

  10. 如果找不到配置文件,使用 DefaultConfiguration 自动配置,将日志输出到控制台。

这段更长的文字当然也不用看,我们就把 log4j2.xml 放入 Classpath 根目录就可以了

log4j2 配置文件编写

log4j 也是 Logger 与 Appender 配置项,也有一个ROOT的特殊 Logger,Appender 比logback支持更多输出位置,如kafka、Cassandra、Flume等。​​​​​​​

<?xml version="1.0" encoding="UTF-8"?><Configuration status="debug" strict="true">    <!--  定义变量,可以被${xxx}引用  -->    <Properties>        <Property name="baseDir">logs</Property>    </Properties>
    <!--  定义 Appenders 用来指定输出位置 -->    <Appenders>        <!-- 日志滚动 $${date:yyyy-MM}:按月滚动文件夹 按小时、文件序号滚动,每次滚动都使用gz压缩 -->        <RollingFile name="RollingFile" fileName="${baseDir}/log.log"                     filePattern="${baseDir}/$${date:yyyy-MM}/log-%d{yyyy-MM-dd-HH}-%i.log.gz">            <!-- 日志格式 -->            <PatternLayout pattern="%d %p %c{1.} [%t] %m%n"/>            <Policies>                <!-- 时间滚动(按月滚动目录,按小时滚动文件) -->                <TimeBasedTriggeringPolicy/>                <!-- 文件大小滚动(1小时内超过250M,强制滚动一次) -->                <SizeBasedTriggeringPolicy size="250 MB"/>            </Policies>            <!-- 每天最多100个文件 -->            <DefaultRolloverStrategy max="100">                <!-- 删除策略,超过三十天删除,如果总文件小于100G,文件数量小于10个,则不会被删除 -->                <Delete basePath="${baseDir}" maxDepth="2">                    <IfFileName glob="*/app-*.log.gz">                        <IfLastModified age="30d">                            <IfAny>                                <IfAccumulatedFileSize exceeds="100 GB"/>                                <IfAccumulatedFileCount exceeds="10"/>                            </IfAny>                        </IfLastModified>                    </IfFileName>                </Delete>            </DefaultRolloverStrategy>        </RollingFile>    </Appenders>
    <Loggers>        <!-- 多个logger -->        <Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
            <AppenderRef ref="RollingFile"/>        </Logger>
        <!-- 一个ROOT -->        <Root level="trace">            <AppenderRef ref="STDOUT"/>        </Root>    </Loggers></Configuration>

可以看得出 log4j2 与 logback 配置文件书写大同小异,甚至同样需要注意additivity="true"时导致的日志重复输出问题,毕竟 log4j1 与 logback 都是 Ceki大神都作品。

总结

得益于 Ceki 大佬的努力,日志使用几乎没有有差异(Logback 与 Log4j2,Google 于 2018年4月开源了流式(fluent)日志框架 Flogger,Slf4j 也将在 2.0 版本支持,而Log4j2再次落后,不过笔者认为log4j2更强大,更多内容请关注下一篇文章)。关于日志如何输出本人也是经验之谈,免不了纰漏,欢迎补充指正,另外每个公司都有不同的应用场景,具体应该遵守公司统一规范。

参考

https://logging.apache.org/log4j/2.x/manual/configuration.html

http://logback.qos.ch/manual/configuration.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/501928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D开发工具HOOPS最新解析合集:助力实现web端高性能模型渲染

一、3D技术为创新提供强大助力&#xff08;1&#xff09;3D专家提供专属技术支持服务 不管您想搭建桌面、WEB或者移动端APP应用&#xff0c;技术领先全球的HOOPS Platform组件都可以为您提供弹性的3D集成架构&#xff0c;同时&#xff0c;一批可信任的工业领域3D技术专家也将为…

Dubbo 基于xml文件分析主流程 (4)

目录 前提 JDK实现SPI Dubbo实现SPI Dubbo源码 1. 找到Dubbo的命名空间处理类&#xff0c;也就是Dubbo的入口类 2. 将dubbo标签交给spring进行管理&#xff0c;就是从 BeanDefinition----> Bean的过程。 3. 服务暴露 4. 服务引入 总结 仿写Dubbo 前提 1. Dubbo源码…

Information-Theoretic Segmentation by Inpainting Error Maximization总结笔记

一、《Inpainting Error Maximization》提出的提问和解决方法 《Inpainting Error Maximization》提出的提问&#xff1a;神经网络通常需要大量的手动标记的训练数据来达到最先进的性能。对于标记数据稀缺或昂贵的问题的适用性往往取决于从 相关领域迁移学习到的表示的能力。 …

在Ubuntu系统中安装JDK 17并配置环境变量

文章目录 打开终端&#xff0c;更新Ubuntu软件包列表&#xff1a;安装OpenJDK 17&#xff1a;检查JDK是否正确安装&#xff1a;配置环境变量&#xff1a; 结语 在Ubuntu系统中安装JDK 17并配置环境变量&#xff0c;可以按照以下步骤进行&#xff1a; 打开终端&#xff0c;更新U…

基于SpringBoot+Vue的宠物管理系统了解一下

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

C/C++每日一练(20230508) 相交链表、字符数组、排序链表

目录 1. 相交链表 &#x1f31f;&#x1f31f; 2. 字符数组 ※ 3. 排序链表 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 相交链表 给你两个单链表…

【Sentinel 规则持久化配置】

Sentinel 规则持久化 Sentinel 规则持久化一、修改order-service服务1.引入依赖2.配置nacos地址 二、修改sentinel-dashboard源码1. 解压2. 修改nacos依赖3. 添加nacos支持4. 修改nacos地址5. 配置nacos数据源6. 修改前端页面7. 重新编译、打包项目8.启动 Sentinel 规则持久化 …

Vulfocus-struts2初了解

CVE-2013-2135 漏洞原理&#xff1a; 配置了通配符*&#xff0c;访问name.action时使用name.jsp来渲染页面&#xff0c;但是在提取name解析时&#xff0c;对其执行了OGNL表达式解析&#xff0c;所以导致了命令执行。如果一个请求与任何其他定义的操作不匹配&#xff0c;它将匹…

注意力机制之SGE Attention

论文 Spatial Group-wise Enhance: Improving Semantic Feature Learning in Convolutional Networks 论文链接 paper:Spatial Group-wise Enhance: Improving Semantic Feature Learning in Convolutional Networks 模型结构 论文主要内容 卷积神经网络&#xff08;CNN&a…

uboot的环境变量相关源码分析

一、uboot的环境变量基础 1.1、环境变量的作用 (1)让我们可以不用修改uboot的源代码&#xff0c;而是通过修改环境变量就可以影响uboot运行时的一些特性。譬如说修改bootdelay环境变量就可以更改系统开机自动启动时倒计时的秒数。 1.2、环境变量的优先级 环境变量的优先级高…

c++(日期类)

本章主要以日期类为例&#xff0c;练习重载各种运算符&#xff0c;需要重点掌握&#xff1a; 1、日期类的<、 <、 、>、 >、 !、重载 2、日期类的 、 、-、-、、--、重载 3、日期类 - 日期类 4、日期类的 << 、>>重载 5、权限问题 目录 1、运算符…

DOM事件(下)

事件执行机制 ●今天来聊一聊事件的执行机制 ●什么是事件的执行机制呢&#xff1f; ○思考一个问题&#xff1f; ○当一个大盒子嵌套一个小盒子的时候&#xff0c;并且两个盒子都有点击事件 ○你点击里面的小盒子&#xff0c;外面的大盒子上的点击事件要不要执行 事件的传播&…

ASEMI代理ADM706SARZ-REEL原装ADI车规级ADM706SARZ-REEL

编辑&#xff1a;ll ASEMI代理ADM706SARZ-REEL原装ADI车规级ADM706SARZ-REEL 型号&#xff1a;ADM706SARZ-REEL 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOIC-8 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;8 工作温度:-40C~85C …

JavaWeb《后端内容:2. MVC-IOC-ServletContext-事务管理-过滤器Filter》

1. 准备和回顾 本篇基于上一篇JavaWeb《后端内容&#xff1a;1. Tomcat - Servlet - Thymeleaf》 继续使用mvc进行优化&#xff0c;复制上面模块的代码&#xff0c;并新建工件和项目和配置服务器 这里可以再好好复习揣摩一下这里index页面的逻辑部分&#xff0c;尤其是关键字的…

PostgreSQL类型系统——Data Types

PostgreSQL Data Types PostgreSQL has a rich set of native data types available to users. Users can add new types to PostgreSQL using the CREATE TYPE command. PostgreSQL有一组丰富的本地数据类型可供用户使用。用户可以使用CREATE TYPE命令向PostgreSQL添加新类型…

[Gitops--12]微服务项目发布

微服务项目发布 1. 微服务项目发布 [流水线] [创建] [下一步] [创建] 1.1 mall-gateway 确认项目中的路由配置都正确 mall-gateway/src/main/resources/application.yml如果不一样就批量替换一下,一共7处 1.2 mall-auth-server mall-auth-server1.3 mall-cart 1.4 mall-c…

ChatGLM-LLaMA-chinese-insturct 学习记录(含LoRA的源码理解)

ChatGLM-LLaMA-chinese-insturct 前言一、实验记录1.1 环境配置1.2 代码理解1.2.1 LoRA 1.4 实验结果 二、总结 前言 介绍&#xff1a;探索中文instruct数据在ChatGLM, LLaMA等LLM上微调表现&#xff0c;结合PEFT等方法降低资源需求。 Github: https://github.com/27182812/Ch…

Win10任务栏透明,3个超好用解决方法!

案例&#xff1a;win10任务栏透明怎么办&#xff1f; 【我的电脑不知道为什么任务栏突然就变透明了&#xff0c;现在不知道该如何解决&#xff0c;遇到这种情况应该怎么办呀&#xff1f;】 Win10任务栏是Windows 10操作系统的一部分&#xff0c;通常默认为不透明。然而&#…

asp.net+sqlserver企业公司进销存管理系统

基于WEB的进销存管理系统主要企业内部提供服务&#xff0c;系统分为管理员&#xff0c;和员工2部分。 在本基于WEB的进销存管理系统中分为管理员&#xff0c;和普通用户2中模式&#xff0c;其中管理人员主要是对企业内商品类型。商品信息商品的出入库信息&#xff0c;以及员工…

堆栈溢出一般是什么原因?

堆栈是一个在计算机科学中经常使用的抽象数据类型。堆栈中的物体具有一个特性&#xff1a; 最后一个放入堆栈中的物体总是被最先拿出来&#xff0c; 这个特性通常称为后进先出(LIFO)队列。 堆栈中定义了一些操作。 两个最重要的是PUSH和POP。 PUSH操作在堆栈的顶部加入一 个元素…