219

I have a website that every time a user logs in or logs out I save it to a text file.

My code doesn't work in appending data or creating a text file if it does not exist.. Here is the sample code

$myfile = fopen("logs.txt", "wr") or die("Unable to open file!");
$txt = "user id date";
fwrite($myfile, $txt);
fclose($myfile);

It seems it does not append to next line after I open it again.

Also I think it would also have an error in a situation when 2 users login at the same time, would it affect opening the text file and saving it afterwards?

Vladislav Povorozniuc
  • 2,149
  • 25
  • 26
Jerahmeel Acebuche
  • 2,209
  • 2
  • 12
  • 8

7 Answers7

432

Try something like this:

 $txt = "user id date";
 $myfile = file_put_contents('logs.txt', $txt.PHP_EOL , FILE_APPEND | LOCK_EX);
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
SpencerX
  • 5,453
  • 1
  • 14
  • 21
  • 7
    does this create a text file if it does not exist? – Jerahmeel Acebuche Jul 26 '14 at 15:19
  • 6
    Yup it create the text file `logs.txt` and append the content in it – SpencerX Jul 26 '14 at 15:21
  • 6
    i have a question. using the LOCK_EX. i know that this will prevent anyone else writing to the file at the same time. but what will happen to the another one? – Jerahmeel Acebuche Jul 26 '14 at 15:51
  • I didn't quite understood your question! – SpencerX Jul 26 '14 at 15:54
  • sorry. example. two users login to the website at the same time in different computer. then that will save the log on the text file at the same time right. now you use the lock_ex to prevent anyone else writing on the file at the same time. this means it was opened and edited on the same time when the two users login. – Jerahmeel Acebuche Jul 26 '14 at 15:57
  • If you want to write to the file regardless, just remove the LOCK_EX since you don't need it and the last person to access your website/script will be the one who will have the data written to the file. Also, since it does append the content well just remove it. (Answer edited) – SpencerX Jul 26 '14 at 16:01
  • according to php manual http://php.net/manual/en/function.file-put-contents.php FILE_APPEND If file already exists, append the data to the file instead of overwriting it. – iman Jun 26 '15 at 13:28
  • 1
    Confirmed - Elegant and quick solution to a basic logging problem I've been having. I dont wanna connect to MySQL... Just wanna drop some text. Perfect. Thanks. – Andy Mar 17 '16 at 03:16
  • 20
    http://php.net/manual/en/function.file-put-contents.php This function returns the number of bytes that were written to the file, or FALSE on failure. Just if anyone's wondering. – Master James Nov 25 '16 at 10:23
  • 9
    @JerahmeelAcebuche The second process to attempt to acquire the lock "will block until the requested lock is acquired" ([source](http://php.net/manual/en/function.flock.php)). In other words, the second process will wait until the first one has closed the file and released the lock. – rinogo Apr 06 '18 at 16:08
  • @rinogo, I don't see `flock()` in this answer. – AaA Nov 12 '18 at 03:28
  • @AaA - The behavior of `file_put_contents()` is influenced by the behavior of `flock()`. – rinogo Nov 12 '18 at 14:57
  • @rinogo, appreciate a reference to article mentioning it – AaA Nov 19 '18 at 10:03
  • 1
    @AaA Please reference the [php.net page for `file_put_contents()`](http://php.net/manual/en/function.file-put-contents.php). Search for `flock()` or `LOCK_EX`. – rinogo Nov 19 '18 at 16:40
  • do I need to use the `LOC_EX` flag if i have multiple users who are writing to the same file? randomly – Muhammad Omer Aslam May 22 '19 at 16:45
  • As per @MasterJames comment, `$myFile =` is a misleading name for the return value. `$nBytesWritten` would be better (but test it for `false`). – ToolmakerSteve Jun 18 '19 at 23:05
  • @SpencerX https://stackoverflow.com/questions/62169939/how-to-export-file-in-dat-format-using-php – Siva Jun 03 '20 at 14:36
128

Use the a mode. It stands for append.

$myfile = fopen("logs.txt", "a") or die("Unable to open file!");
$txt = "user id date";
fwrite($myfile, "\n". $txt);
fclose($myfile);
Valentin Mercier
  • 5,256
  • 3
  • 26
  • 50
  • i also have this issue of the \n . it does not work. does it work to you? – Jerahmeel Acebuche Jul 26 '14 at 15:27
  • 1
    does this work if the situation happens as i have said ? – Jerahmeel Acebuche Jul 26 '14 at 15:28
  • 1
    the `a` flag will create the file if it does not exists and will no matter what make sure that you really `append` new text. For the `\n` it works but only if inside double quotes `"` not single quotes `'` – Valentin Mercier Jul 26 '14 at 15:30
  • i mean the situation of what if the two users login in different browser. then the php will open the file on the two browser on the same time and update it on the same time also. will there be a problem?\ – Jerahmeel Acebuche Jul 26 '14 at 15:44
  • 1
    Oh, yes there will be, in this case use a SQL database, it has a built-in engine to avoid race-conditions. But that's a whole new question. – Valentin Mercier Jul 26 '14 at 15:45
  • http://php.net/manual/en/function.file-put-contents.php This function is identical to calling fopen(), fwrite() and fclose() successively to write data to a file. – Master James Nov 25 '16 at 10:21
  • On my system (php7) file_put_contents( $file, $string, FILE_APPEND | LOCK_EX ); is ~50% faster than $h = fopen( $file, "a"); fwrite( $h, $string ); fclose( $h ); – Torben Nov 13 '18 at 22:58
  • @ValentinMercier please help https://stackoverflow.com/questions/62169939/how-to-export-file-in-dat-or-txt-format-using-php – Siva Jun 04 '20 at 13:45
18

You can do it the OO way, just an alternative and flexible:

class Logger {

    private
        $file,
        $timestamp;

    public function __construct($filename) {
        $this->file = $filename;
    }

    public function setTimestamp($format) {
        $this->timestamp = date($format)." » ";
    }

    public function putLog($insert) {
        if (isset($this->timestamp)) {
            file_put_contents($this->file, $this->timestamp.$insert."<br>", FILE_APPEND);
        } else {
            trigger_error("Timestamp not set", E_USER_ERROR);
        }
    }

    public function getLog() {
        $content = @file_get_contents($this->file);
        return $content;
    }

}

Then use it like this .. let's say you have user_name stored in a session (semi pseudo code):

$log = new Logger("log.txt");
$log->setTimestamp("D M d 'y h.i A");

if (user logs in) {
    $log->putLog("Successful Login: ".$_SESSION["user_name"]);
}
if (user logs out) {
    $log->putLog("Logout: ".$_SESSION["user_name"]);
}

Check your log with this:

$log->getLog();

Result is like:

Sun Jul 02 '17 05.45 PM » Successful Login: JohnDoe
Sun Jul 02 '17 05.46 PM » Logout: JohnDoe


github.com/thielicious/Logger

Thielicious
  • 4,122
  • 2
  • 25
  • 35
  • 2
    Many thanks for this Logger class! I was not expecting to find that when I found this page... but it's actually the foundation of a much better solution than what I was originally planning to implement. – Myke Carter Jun 13 '19 at 04:27
8

This is working for me, Writing(creating as well) and/or appending content in the same mode.

$fp = fopen("MyFile.txt", "a+") 
Mihir Bhatt
  • 3,019
  • 2
  • 37
  • 41
5

Although there are many ways to do this. But if you want to do it in an easy way and want to format text before writing it to log file. You can create a helper function for this.

if (!function_exists('logIt')) {
    function logIt($logMe)
    {
        $logFilePath = storage_path('logs/cron.log.'.date('Y-m-d').'.log');
        $cronLogFile = fopen($logFilePath, "a");
        fwrite($cronLogFile, date('Y-m-d H:i:s'). ' : ' .$logMe. PHP_EOL);
        fclose($cronLogFile);
    }
}
PHP Worm...
  • 4,109
  • 1
  • 25
  • 48
4

Try this code:

function logErr($data){
  $logPath = __DIR__. "/../logs/logs.txt";
  $mode = (!file_exists($logPath)) ? 'w':'a';
  $logfile = fopen($logPath, $mode);
  fwrite($logfile, "\r\n". $data);
  fclose($logfile);
}

I always use it like this, and it works...

zx485
  • 28,498
  • 28
  • 50
  • 59
Rwakos
  • 71
  • 3
  • 1
    There is no reason to do `(!file_exists($logPath)) ? 'w':'a'` - the `a` option already has the correct behavior when file does not exist. So can simplify this answer by removing line `$mode = ...;`, and changing next line to `$logfile = fopen($logPath, 'a');`. – ToolmakerSteve Jun 18 '19 at 22:26
2

There is no such file open mode as "wr" in your code:

fopen("logs.txt", "wr") 

The file open modes in PHP http://php.net/manual/en/function.fopen.php is the same as in C: http://www.cplusplus.com/reference/cstdio/fopen/

There are the following main open modes "r" for read, "w" for write and "a" for append, and you cannot combine them. You can add other modifiers like "+" for update, "b" for binary. The new C standard adds a new standard subspecifier ("x"), supported by PHP, that can be appended to any "w" specifier (to form "wx", "wbx", "w+x" or "w+bx"/"wb+x"). This subspecifier forces the function to fail if the file exists, instead of overwriting it.

Besides that, in PHP 5.2.6, the 'c' main open mode was added. You cannot combine 'c' with 'a', 'r', 'w'. The 'c' opens the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). 'c+' Open the file for reading and writing; otherwise it has the same behavior as 'c'.

Additionally, and in PHP 7.1.2 the 'e' option was added that can be combined with other modes. It set close-on-exec flag on the opened file descriptor. Only available in PHP compiled on POSIX.1-2008 conform systems.

So, for the task as you have described it, the best file open mode would be 'a'. It opens the file for writing only. It places the file pointer at the end of the file. If the file does not exist, it attempts to create it. In this mode, fseek() has no effect, writes are always appended.

Here is what you need, as has been already pointed out above:

fopen("logs.txt", "a") 
Maxim Masiutin
  • 3,991
  • 4
  • 55
  • 72